import {
  TERMINAL_COMMAND_SPECS,
  TerminalCommandArgumentSpec,
  TerminalCommandSpec,
} from './terminalCommandSpecs';

export interface TerminalShadowArgumentState {
  spec: TerminalCommandArgumentSpec;
  value: string;
  isFilled: boolean;
  isActive: boolean;
}

export interface TerminalShadowState {
  spec: TerminalCommandSpec;
  mode: 'exact' | 'predictive';
  arguments: TerminalShadowArgumentState[];
  currentArgumentIndex: number;
  helperText: string;
  signature: string;
  readyToRun: boolean;
  hasExtraInput: boolean;
}

const SPECS_BY_LENGTH = [...TERMINAL_COMMAND_SPECS].sort(
  (left, right) => right.command.length - left.command.length,
);

const getArgumentToken = (
  argument: TerminalCommandArgumentSpec,
  value?: string,
): string => {
  const normalizedValue = value?.trim();
  if (normalizedValue) {
    return normalizedValue;
  }

  return argument.required
    ? `<${argument.placeholder}>`
    : `[${argument.placeholder}]`;
};

const parseProvidedValues = (
  spec: TerminalCommandSpec,
  input: string,
): { values: string[]; hasExtraInput: boolean } => {
  const remainder = input.slice(spec.command.length);
  const trimmedRemainder = remainder.trim();

  if (spec.arguments.length === 0) {
    return {
      values: [],
      hasExtraInput: trimmedRemainder.length > 0,
    };
  }

  const restArgumentIndex = spec.arguments.findIndex(
    (argument) => argument.consumeRest,
  );

  if (restArgumentIndex === 0) {
    return {
      values: trimmedRemainder ? [trimmedRemainder] : [''],
      hasExtraInput: false,
    };
  }

  const tokens = trimmedRemainder ? trimmedRemainder.split(/\s+/) : [];
  const values = spec.arguments.map((_, index) => tokens[index] ?? '');

  return {
    values,
    hasExtraInput: tokens.length > spec.arguments.length,
  };
};

export const buildCommandSignature = (
  spec: TerminalCommandSpec,
  values: string[] = [],
): string => {
  if (spec.arguments.length === 0) {
    return spec.command;
  }

  const tokens = spec.arguments.map((argument, index) =>
    getArgumentToken(argument, values[index]),
  );

  return `${spec.command} ${tokens.join(' ')}`;
};

export const resolveTerminalShadow = (
  rawInput: string,
): TerminalShadowState | null => {
  const input = rawInput.trimStart();
  if (!input.startsWith('/')) {
    return null;
  }

  const predictiveInput = input.trimEnd();
  const predictiveSpec = SPECS_BY_LENGTH.find(
    (spec) =>
      predictiveInput.length > 0 &&
      spec.command !== predictiveInput &&
      spec.command.startsWith(predictiveInput),
  );

  const exactSpec = SPECS_BY_LENGTH.find(
    (spec) => input === spec.command || input.startsWith(`${spec.command} `),
  );

  if (!exactSpec && !predictiveSpec) {
    return null;
  }

  if (
    predictiveSpec &&
    exactSpec &&
    predictiveInput !== exactSpec.command &&
    predictiveSpec.command.startsWith(predictiveInput)
  ) {
    return {
      spec: predictiveSpec,
      mode: 'predictive',
      arguments: predictiveSpec.arguments.map((argument, index) => ({
        spec: argument,
        value: '',
        isFilled: false,
        isActive: index === 0,
      })),
      currentArgumentIndex: predictiveSpec.arguments.length > 0 ? 0 : -1,
      helperText:
        predictiveSpec.arguments.length > 0
          ? `Press Tab to complete ${predictiveSpec.command} and start with ${predictiveSpec.arguments[0].label.toLowerCase()}.`
          : `Press Tab to complete ${predictiveSpec.command}.`,
      signature: buildCommandSignature(predictiveSpec),
      readyToRun: predictiveSpec.arguments.length === 0,
      hasExtraInput: false,
    };
  }

  const spec = exactSpec ?? predictiveSpec;
  if (!spec) {
    return null;
  }

  if (!exactSpec) {
    return {
      spec,
      mode: 'predictive',
      arguments: spec.arguments.map((argument, index) => ({
        spec: argument,
        value: '',
        isFilled: false,
        isActive: index === 0,
      })),
      currentArgumentIndex: spec.arguments.length > 0 ? 0 : -1,
      helperText:
        spec.arguments.length > 0
          ? `Press Tab to complete ${spec.command} and start with ${spec.arguments[0].label.toLowerCase()}.`
          : `Press Tab to complete ${spec.command}.`,
      signature: buildCommandSignature(spec),
      readyToRun: spec.arguments.length === 0,
      hasExtraInput: false,
    };
  }

  const { values, hasExtraInput } = parseProvidedValues(spec, input);
  const firstMissingRequiredIndex = spec.arguments.findIndex(
    (argument, index) => argument.required && !values[index]?.trim(),
  );
  const nextOptionalIndex = spec.arguments.findIndex(
    (_, index) => !values[index]?.trim(),
  );

  let currentArgumentIndex = -1;
  if (firstMissingRequiredIndex >= 0) {
    currentArgumentIndex = firstMissingRequiredIndex;
  } else if (nextOptionalIndex >= 0) {
    currentArgumentIndex = nextOptionalIndex;
  }

  const readyToRun =
    !hasExtraInput &&
    spec.arguments.every(
      (argument, index) => !argument.required || Boolean(values[index]?.trim()),
    );

  const helperText = hasExtraInput
    ? `Too many arguments. Expected ${buildCommandSignature(spec)}.`
    : currentArgumentIndex >= 0
      ? spec.arguments[currentArgumentIndex].required
        ? `Next: enter ${spec.arguments[currentArgumentIndex].label.toLowerCase()}.`
        : `Optional: add ${spec.arguments[currentArgumentIndex].label.toLowerCase()} or press Enter to run.`
      : spec.arguments.length === 0
        ? `${spec.command} is ready to run.`
        : 'Ready to run.';

  return {
    spec,
    mode: 'exact',
    arguments: spec.arguments.map((argument, index) => ({
      spec: argument,
      value: values[index] ?? '',
      isFilled: Boolean(values[index]?.trim()),
      isActive: index === currentArgumentIndex,
    })),
    currentArgumentIndex,
    helperText,
    signature: buildCommandSignature(spec, values),
    readyToRun,
    hasExtraInput,
  };
};
