/**
 * Vim command parser
 * Handles: [count][operator][count][motion/text-object] and [count][action]
 * Examples: 3dw, d3w, 2ciw, c2aw, p, 3p, P, guw, gUiw, gJ, etc.
 */

import { getActionKeys } from "../actions/action-registry";
import { getOperatorKeys } from "../operators/operator-registry";
import { getMotionKeys } from "./motion-registry";
import type { VimCommand } from "./types";

interface ParseState {
  count1?: number; // Count before operator/action
  operator?: string;
  count2?: number; // Count after operator
  textObjectMode?: "inner" | "around";
  motion?: string;
  textObject?: string;
  action?: string;
}

type ParseStatus = "complete" | "incomplete" | "invalid";

interface ParseResult {
  status: ParseStatus;
  command?: VimCommand;
}

interface MotionMatchResult {
  status: "complete" | "partial" | "none";
  motion?: string;
  length?: number;
}

interface MultiKeyMatchResult {
  status: "complete" | "partial" | "none";
  key?: string;
  length?: number;
}

let cachedMotionKeys: string[] | null = null;
const motionKeysDescending = (): string[] => {
  if (!cachedMotionKeys) {
    cachedMotionKeys = [...getMotionKeys()].sort((a, b) => b.length - a.length);
  }
  return cachedMotionKeys;
};

let cachedOperatorKeys: string[] | null = null;
const operatorKeysDescending = (): string[] => {
  if (!cachedOperatorKeys) {
    cachedOperatorKeys = [...getOperatorKeys()].sort((a, b) => b.length - a.length);
  }
  return cachedOperatorKeys;
};

let cachedActionKeys: string[] | null = null;
const actionKeysDescending = (): string[] => {
  if (!cachedActionKeys) {
    cachedActionKeys = [...getActionKeys()].sort((a, b) => b.length - a.length);
  }
  return cachedActionKeys;
};

const matchMultiKey = (
  keys: string[],
  startIndex: number,
  registeredKeys: string[],
): MultiKeyMatchResult => {
  const remaining = keys.slice(startIndex);
  if (remaining.length === 0) {
    return { status: "partial" };
  }

  const remainingString = remaining.join("");
  let hasPartialMatch = false;

  for (const regKey of registeredKeys) {
    if (regKey.length <= remaining.length) {
      const candidate = remaining.slice(0, regKey.length).join("");
      if (candidate === regKey) {
        return {
          status: "complete",
          key: regKey,
          length: regKey.length,
        };
      }
    } else if (regKey.startsWith(remainingString)) {
      hasPartialMatch = true;
    }
  }

  if (hasPartialMatch) {
    return { status: "partial" };
  }

  return { status: "none" };
};

const matchMotion = (keys: string[], startIndex: number): MotionMatchResult => {
  const result = matchMultiKey(keys, startIndex, motionKeysDescending());
  return {
    status: result.status,
    motion: result.key,
    length: result.length,
  };
};

const matchOperator = (keys: string[], startIndex: number): MultiKeyMatchResult => {
  return matchMultiKey(keys, startIndex, operatorKeysDescending());
};

const matchAction = (keys: string[], startIndex: number): MultiKeyMatchResult => {
  return matchMultiKey(keys, startIndex, actionKeysDescending());
};

const parseNumber = (keys: string[], index: number): { value?: number; nextIndex: number } => {
  let currentIndex = index;
  if (currentIndex >= keys.length) {
    return { nextIndex: currentIndex };
  }

  if (!/[1-9]/.test(keys[currentIndex])) {
    return { nextIndex: currentIndex };
  }

  let countStr = keys[currentIndex];
  currentIndex++;
  while (currentIndex < keys.length && /[0-9]/.test(keys[currentIndex])) {
    countStr += keys[currentIndex];
    currentIndex++;
  }

  return {
    value: parseInt(countStr, 10),
    nextIndex: currentIndex,
  };
};

const parseVimCommandInternal = (keys: string[]): ParseResult => {
  if (keys.length === 0) {
    return { status: "incomplete" };
  }

  const state: ParseState = {};
  let index = 0;

  // Parse first count (before operator/action)
  const firstCount = parseNumber(keys, index);
  if (firstCount.value !== undefined) {
    state.count1 = firstCount.value;
  }
  index = firstCount.nextIndex;

  if (index >= keys.length) {
    return { status: "incomplete" };
  }

  // Try multi-char action first (e.g., gJ)
  const actionMatch = matchAction(keys, index);
  if (actionMatch.status === "complete" && actionMatch.key && actionMatch.length) {
    if (index + actionMatch.length === keys.length) {
      state.action = actionMatch.key;
      const command: VimCommand = {};
      if (state.count1) {
        command.count = state.count1;
      }
      command.action = state.action;
      return { status: "complete", command };
    }
    // Extra keys after a complete action is invalid
    return { status: "invalid" };
  }

  // Try multi-char operator (e.g., gu, gU)
  const operatorMatch = matchOperator(keys, index);
  if (operatorMatch.status === "complete" && operatorMatch.key && operatorMatch.length) {
    state.operator = operatorMatch.key;
    index += operatorMatch.length;

    if (index >= keys.length) {
      return { status: "incomplete" };
    }
  } else if (operatorMatch.status === "partial") {
    // Could be start of operator or action, check if action also partial
    if (actionMatch.status === "partial") {
      return { status: "incomplete" };
    }
    // Only operator is partial
    return { status: "incomplete" };
  }

  // If no operator matched yet and action was "none", fall through to motion-only parsing
  // This handles cases like plain motions (j, k, w, etc.)

  if (!state.operator && operatorMatch.status === "none" && actionMatch.status === "none") {
    // No operator or action matched - try as a pure motion
    // But first check if it could be a partial action/operator (already handled above)
  }

  // Parse second count (after operator)
  if (state.operator) {
    const secondCount = parseNumber(keys, index);
    if (secondCount.value !== undefined) {
      state.count2 = secondCount.value;
    }
    index = secondCount.nextIndex;

    if (index >= keys.length) {
      return { status: "incomplete" };
    }
  }

  // Parse text object mode (i or a) - only valid after an operator
  if (state.operator && (keys[index] === "i" || keys[index] === "a")) {
    state.textObjectMode = keys[index] as "inner" | "around";
    index++;

    if (index >= keys.length) {
      return { status: "incomplete" };
    }

    state.textObject = keys[index];
    index++;
  } else if (state.operator && keys.slice(index).join("") === state.operator) {
    // Handle doubled operator (dd, yy, cc, etc.)
    // For multi-char operators like "gu", doubled would be "gugu"
    const opLen = state.operator.length;
    const remainingStr = keys.slice(index).join("");
    if (remainingStr === state.operator) {
      state.motion = state.operator;
      index += opLen;
    } else if (state.operator.length === 1 && keys[index] === state.operator) {
      state.motion = state.operator;
      index++;
    } else {
      // Parse motion
      const motionMatch = matchMotion(keys, index);
      if (motionMatch.status === "partial") {
        return { status: "incomplete" };
      }
      if (motionMatch.status === "none" || !motionMatch.motion || !motionMatch.length) {
        return { status: "invalid" };
      }

      state.motion = motionMatch.motion;
      index += motionMatch.length;
    }
  } else {
    // Parse motion (supports multi-key motions)
    const motionMatch = matchMotion(keys, index);
    if (motionMatch.status === "partial") {
      return { status: "incomplete" };
    }
    if (motionMatch.status === "none" || !motionMatch.motion || !motionMatch.length) {
      return { status: "invalid" };
    }

    state.motion = motionMatch.motion;
    index += motionMatch.length;
  }

  if (index !== keys.length) {
    return { status: "invalid" };
  }

  const command: VimCommand = {};

  // Combine counts (count1 * count2)
  if (state.count1 && state.count2) {
    command.count = state.count1 * state.count2;
  } else if (state.count1) {
    command.count = state.count1;
  } else if (state.count2) {
    command.count = state.count2;
  }

  if (state.operator) {
    command.operator = state.operator;
  }

  if (state.textObject && state.textObjectMode) {
    command.textObject = {
      mode: state.textObjectMode,
      object: state.textObject,
    };
  } else if (state.motion) {
    command.motion = state.motion;
  }

  if (command.operator && !(command.motion || command.textObject)) {
    return { status: "incomplete" };
  }

  if (!command.operator && !command.motion) {
    return { status: "invalid" };
  }

  return {
    status: "complete",
    command,
  };
};

/**
 * Parse a vim command sequence
 */
export const parseVimCommand = (keys: string[]): VimCommand | null => {
  const result = parseVimCommandInternal(keys);
  return result.status === "complete" ? (result.command ?? null) : null;
};

/**
 * Get the effective count from a vim command
 */
export const getEffectiveCount = (command: VimCommand): number => {
  return command.count || 1;
};

/**
 * Check if a command is complete
 */
export const isCommandComplete = (keys: string[]): boolean => {
  return parseVimCommandInternal(keys).status === "complete";
};

/**
 * Check if more keys are expected
 */
export const expectsMoreKeys = (keys: string[]): boolean => {
  return parseVimCommandInternal(keys).status === "incomplete";
};

/**
 * Get current parse status for a key sequence
 */
export const getCommandParseStatus = (keys: string[]): ParseStatus => {
  return parseVimCommandInternal(keys).status;
};
