import type { EditorView } from "@codemirror/view";
import type { Diagnostic } from "@codemirror/lint";
import type { TableSchema } from "@internal/tsql";
import { parseTSQLSelect, SyntaxError, QueryError, validateQuery } from "@internal/tsql";

/**
 * Configuration for the TSQL linter
 */
export interface TSQLLinterConfig {
  /** Optional schema for validating table/column names */
  schema?: TableSchema[];
  /** Delay in milliseconds before running the linter (debouncing) */
  delay?: number;
}

/**
 * Extract line and column from a TSQL error message
 * Error format: "Syntax error at line X:Y: message"
 */
function parseErrorPosition(message: string): { line: number; column: number } | null {
  const match = message.match(/at line (\d+):(\d+)/);
  if (match) {
    return {
      line: parseInt(match[1], 10),
      column: parseInt(match[2], 10),
    };
  }
  return null;
}

/**
 * Convert line/column to a document position
 */
function positionToOffset(doc: string, line: number, column: number): number {
  const lines = doc.split("\n");

  // line is 1-indexed
  let offset = 0;
  for (let i = 0; i < line - 1 && i < lines.length; i++) {
    offset += lines[i].length + 1; // +1 for newline
  }

  return offset + column;
}

/**
 * Find the end of a word/token at the given position
 */
function findTokenEnd(doc: string, start: number): number {
  let end = start;

  // Scan forward until we hit whitespace or end of string
  while (end < doc.length && /\S/.test(doc[end])) {
    end++;
  }

  // If we didn't move, include at least one character
  if (end === start) {
    end = Math.min(start + 1, doc.length);
  }

  return end;
}

/**
 * Create a TSQL linter function for CodeMirror
 *
 * This linter uses the TSQL ANTLR parser to detect syntax errors
 * and optionally validates against a schema.
 *
 * @param config - Linter configuration
 * @returns A linter function for use with CodeMirror's linter extension
 */
export function createTSQLLinter(
  config: TSQLLinterConfig = {}
): (view: EditorView) => Diagnostic[] {
  const { schema = [] } = config;

  return (view: EditorView): Diagnostic[] => {
    const content = view.state.doc.toString().trim();

    // Return no errors for empty content
    if (!content) {
      return [];
    }

    const diagnostics: Diagnostic[] = [];

    try {
      // Try to parse the query
      const ast = parseTSQLSelect(content);

      // If parsing succeeds and we have a schema, run schema validation
      if (schema.length > 0) {
        const validationResult = validateQuery(ast, schema);

        for (const issue of validationResult.issues) {
          // Map validation severity to CodeMirror diagnostic severity
          const severity: "error" | "warning" | "info" =
            issue.severity === "error"
              ? "error"
              : issue.severity === "warning"
                ? "warning"
                : "info";

          diagnostics.push({
            from: 0,
            to: content.length,
            severity,
            message: issue.message,
            source: "tsql",
          });
        }
      }
    } catch (error) {
      if (error instanceof SyntaxError) {
        const position = parseErrorPosition(error.message);

        let from: number;
        let to: number;

        if (position) {
          from = positionToOffset(content, position.line, position.column);
          to = findTokenEnd(content, from);
        } else {
          // If we can't parse the position, highlight the whole query
          from = 0;
          to = content.length;
        }

        // Clean up the error message
        let message = error.message;
        // Remove the "Syntax error at line X:Y: " prefix if present
        message = message.replace(/^Syntax error at line \d+:\d+:\s*/, "");

        diagnostics.push({
          from,
          to,
          severity: "error",
          message: message,
          source: "tsql",
        });
      } else if (error instanceof QueryError) {
        // Schema validation errors don't have position info,
        // so highlight the whole query
        diagnostics.push({
          from: 0,
          to: content.length,
          severity: "warning",
          message: error.message,
          source: "tsql",
        });
      } else if (error instanceof Error) {
        // Unknown error
        diagnostics.push({
          from: 0,
          to: content.length,
          severity: "error",
          message: error.message,
          source: "tsql",
        });
      }
    }

    return diagnostics;
  };
}

/**
 * Check if a TSQL query is valid
 *
 * @param query - The query to validate
 * @returns true if the query is valid, false otherwise
 */
export function isValidTSQLQuery(query: string): boolean {
  if (!query.trim()) {
    return true; // Empty queries are considered valid
  }

  try {
    parseTSQLSelect(query);
    return true;
  } catch {
    return false;
  }
}

/**
 * Get error message for a TSQL query, if any
 *
 * @param query - The query to validate
 * @returns Error message if invalid, null if valid
 */
export function getTSQLError(query: string): string | null {
  if (!query.trim()) {
    return null;
  }

  try {
    parseTSQLSelect(query);
    return null;
  } catch (error) {
    if (error instanceof Error) {
      return error.message;
    }
    return "Unknown error";
  }
}
