import type { ToolInput } from "../../core/io/input-types";
import type { ToolResult } from "../../core/io/output-types";
import type { ToolContext } from "../../core/plugins/plugin-types";
import type { CsvToolsOptions } from "./index";

export function parseCsv(text: string, delimiter: string): string[][] {
  const rows: string[][] = [];
  let currentRow: string[] = [];
  let currentField = "";
  let inQuotes = false;

  for (let i = 0; i < text.length; i++) {
    const char = text[i];
    const nextChar = text[i + 1];

    if (inQuotes) {
      if (char === '"') {
        if (nextChar === '"') {
          // Escaped quote
          currentField += '"';
          i++; // Skip next quote
        } else {
          // Closing quote
          inQuotes = false;
        }
      } else {
        currentField += char;
      }
    } else {
      if (char === '"') {
        inQuotes = true;
      } else if (char === delimiter) {
        currentRow.push(currentField);
        currentField = "";
      } else if (char === "\n" || char === "\r") {
        currentRow.push(currentField);
        currentField = "";
        rows.push(currentRow);
        currentRow = [];
        
        if (char === "\r" && nextChar === "\n") {
          i++; // Skip \n in \r\n
        }
      } else {
        currentField += char;
      }
    }
  }

  // Push final field/row if anything remains
  if (currentField !== "" || currentRow.length > 0) {
    currentRow.push(currentField);
    rows.push(currentRow);
  }

  // Filter out completely empty trailing row (e.g. from file ending with a newline)
  if (rows.length > 0) {
    const lastRow = rows[rows.length - 1];
    if (lastRow.length === 1 && lastRow[0] === "") {
      rows.pop();
    }
  }

  return rows;
}

function escapeCsvField(val: unknown, delimiter: string): string {
  if (val === null || val === undefined) return "";
  const str = String(val);
  const needsQuotes =
    str.includes(delimiter) ||
    str.includes('"') ||
    str.includes("\n") ||
    str.includes("\r");

  if (needsQuotes) {
    return `"${str.replace(/"/g, '""')}"`;
  }
  return str;
}

export async function runCsvTools(
  input: ToolInput,
  options: CsvToolsOptions,
  context: ToolContext
): Promise<ToolResult> {
  const text = (input.text ?? "").trim();
  if (!text) {
    throw new Error("Please enter input text to convert.");
  }

  const { mode, delimiter, hasHeaderRow, prettyJson } = options;

  if (mode === "csv-to-json") {
    context.reportProgress({ percentage: 20, message: "Parsing CSV..." });
    const parsedRows = parseCsv(text, delimiter);

    if (parsedRows.length === 0) {
      return {
        type: "text",
        value: "[]",
        language: "json",
      };
    }

    context.reportProgress({ percentage: 60, message: "Structuring JSON..." });
    
    if (hasHeaderRow) {
      const headers = parsedRows[0].map(h => h.trim());
      const objects: Record<string, string>[] = [];
      
      for (let i = 1; i < parsedRows.length; i++) {
        const row = parsedRows[i];
        const obj: Record<string, string> = {};
        
        for (let j = 0; j < headers.length; j++) {
          obj[headers[j]] = row[j] ?? "";
        }
        objects.push(obj);
      }
      
      context.reportProgress({ percentage: 100, message: "Done" });
      return {
        type: "text",
        value: JSON.stringify(objects, null, prettyJson ? 2 : undefined),
        language: "json",
      };
    } else {
      context.reportProgress({ percentage: 100, message: "Done" });
      return {
        type: "text",
        value: JSON.stringify(parsedRows, null, prettyJson ? 2 : undefined),
        language: "json",
      };
    }
  } else {
    // json-to-csv mode
    context.reportProgress({ percentage: 25, message: "Parsing JSON..." });
    let data: unknown;
    try {
      data = JSON.parse(text);
    } catch (err) {
      throw new Error("Invalid input: Not a valid JSON string. JSON to CSV mode requires a valid JSON array.");
    }

    if (!Array.isArray(data)) {
      throw new Error("Invalid input: JSON content must be an array of objects or an array of arrays.");
    }

    if (data.length === 0) {
      return {
        type: "text",
        value: "",
        language: "plain",
      };
    }

    context.reportProgress({ percentage: 65, message: "Serializing to CSV..." });

    const firstItem = data[0];

    if (Array.isArray(firstItem)) {
      // Array of arrays
      const csvLines = (data as unknown[][]).map(row =>
        row.map(cell => escapeCsvField(cell, delimiter)).join(delimiter)
      );
      context.reportProgress({ percentage: 100, message: "Done" });
      return {
        type: "text",
        value: csvLines.join("\n"),
        language: "plain",
      };
    } else if (typeof firstItem === "object" && firstItem !== null) {
      // Array of objects
      // Collect unique keys across all objects to ensure all properties are included
      const keysSet = new Set<string>();
      data.forEach(item => {
        if (typeof item === "object" && item !== null) {
          Object.keys(item).forEach(k => keysSet.add(k));
        }
      });
      const keys = Array.from(keysSet);

      const headerLine = keys.map(k => escapeCsvField(k, delimiter)).join(delimiter);
      const csvLines = (data as Record<string, unknown>[]).map(item =>
        keys.map(key => escapeCsvField(item[key], delimiter)).join(delimiter)
      );

      context.reportProgress({ percentage: 100, message: "Done" });
      return {
        type: "text",
        value: [headerLine, ...csvLines].join("\n"),
        language: "plain",
      };
    } else {
      throw new Error("Invalid input: Array elements must be either objects or arrays.");
    }
  }
}
