export function readEnvNumber(name, env = process.env) {
  const raw = env[name]?.trim();
  if (!raw) {
    return null;
  }
  const parsed = Number.parseFloat(raw);
  return Number.isFinite(parsed) ? parsed : null;
}

export function consumeStringFlag(argv, index, flag, currentValue) {
  if (argv[index] !== flag) {
    return null;
  }
  return {
    nextIndex: index + 1,
    value: argv[index + 1] ?? currentValue,
  };
}

export function consumeStringListFlag(argv, index, flag) {
  if (argv[index] !== flag) {
    return null;
  }
  const value = argv[index + 1];
  return {
    nextIndex: index + 1,
    value: typeof value === "string" && value.length > 0 ? value : null,
  };
}

export function consumeIntFlag(argv, index, flag, currentValue, options = {}) {
  if (argv[index] !== flag) {
    return null;
  }
  const parsed = Number.parseInt(argv[index + 1] ?? "", 10);
  const min = options.min ?? Number.NEGATIVE_INFINITY;
  return {
    nextIndex: index + 1,
    value: Number.isFinite(parsed) && parsed >= min ? parsed : currentValue,
  };
}

export function consumeFloatFlag(argv, index, flag, currentValue, options = {}) {
  if (argv[index] !== flag) {
    return null;
  }
  const parsed = Number.parseFloat(argv[index + 1] ?? "");
  const min = options.min ?? Number.NEGATIVE_INFINITY;
  const includeMin = options.includeMin ?? true;
  const isValid = Number.isFinite(parsed) && (includeMin ? parsed >= min : parsed > min);
  return {
    nextIndex: index + 1,
    value: isValid ? parsed : currentValue,
  };
}

export function stringFlag(flag, key) {
  return {
    consume(argv, index, args) {
      const option = consumeStringFlag(argv, index, flag, args[key]);
      if (!option) {
        return null;
      }
      return {
        nextIndex: option.nextIndex,
        apply(target) {
          target[key] = option.value;
        },
      };
    },
  };
}

export function stringListFlag(flag, key) {
  return {
    consume(argv, index) {
      const option = consumeStringListFlag(argv, index, flag);
      if (!option) {
        return null;
      }
      return {
        nextIndex: option.nextIndex,
        apply(target) {
          if (option.value) {
            target[key].push(option.value);
          }
        },
      };
    },
  };
}

function createAssignedValueFlag(consumeOption) {
  return {
    consume(argv, index, args) {
      const option = consumeOption(argv, index, args);
      if (!option) {
        return null;
      }
      return {
        nextIndex: option.nextIndex,
        apply(target) {
          target[option.key] = option.value;
        },
      };
    },
  };
}

export function intFlag(flag, key, options) {
  return createAssignedValueFlag((argv, index, args) => {
    const option = consumeIntFlag(argv, index, flag, args[key], options);
    return option ? { ...option, key } : null;
  });
}

export function floatFlag(flag, key, options) {
  return createAssignedValueFlag((argv, index, args) => {
    const option = consumeFloatFlag(argv, index, flag, args[key], options);
    return option ? { ...option, key } : null;
  });
}

export function booleanFlag(flag, key, value = true) {
  return {
    consume(argv, index) {
      if (argv[index] !== flag) {
        return null;
      }
      return {
        nextIndex: index,
        apply(target) {
          target[key] = value;
        },
      };
    },
  };
}

export function parseFlagArgs(argv, args, specs, options = {}) {
  for (let i = 0; i < argv.length; i += 1) {
    const arg = argv[i];
    if (arg === "--" && options.ignoreDoubleDash) {
      continue;
    }
    let handled = false;
    for (const spec of specs) {
      const option = spec.consume(argv, i, args);
      if (!option) {
        continue;
      }
      option.apply(args);
      i = option.nextIndex;
      handled = true;
      break;
    }
    if (handled) {
      continue;
    }
    const fallbackResult = options.onUnhandledArg?.(arg, args);
    if (fallbackResult === "handled") {
      continue;
    }
    if (!options.allowUnknownOptions && arg.startsWith("-")) {
      throw new Error(`Unknown option: ${arg}`);
    }
  }
  return args;
}
