import type { SkillEntry } from "@capakraken/shared";
import { assertHeaderRow, assertTabularMatrixWithinLimits } from "./excel.js";

type ExcelJsModule = typeof import("exceljs");

let _excelJs: ExcelJsModule | null = null;

async function getExcelJS() {
  if (!_excelJs) {
    _excelJs = await import("exceljs");
  }
  return _excelJs;
}

function normalizeCellString(value: unknown): string {
  if (value === undefined || value === null) {
    return "";
  }

  if (value instanceof Date) {
    return value.toISOString();
  }

  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
    return String(value);
  }

  if (typeof value === "object") {
    const record = value as Record<string, unknown>;

    if ("result" in record) {
      return normalizeCellString(record.result);
    }

    if ("text" in record && typeof record.text === "string") {
      return record.text;
    }

    if ("hyperlink" in record && typeof record.hyperlink === "string") {
      return record.hyperlink;
    }

    if ("richText" in record && Array.isArray(record.richText)) {
      return record.richText
        .map((part) => {
          if (part && typeof part === "object" && "text" in part) {
            const text = (part as { text?: unknown }).text;
            return typeof text === "string" ? text : "";
          }
          return "";
        })
        .join("");
    }
  }

  return String(value);
}

function worksheetToRowObjects(
  worksheet: {
    rowCount: number;
    getRow: (rowNumber: number) => {
      cellCount: number;
      getCell: (columnNumber: number) => { value: unknown };
    };
  } | undefined,
): Record<string, string>[] {
  if (!worksheet) {
    return [];
  }

  const rows: string[][] = [];
  for (let rowNumber = 1; rowNumber <= worksheet.rowCount; rowNumber += 1) {
    const row = worksheet.getRow(rowNumber);
    const cells: string[] = [];

    for (let columnNumber = 1; columnNumber <= row.cellCount; columnNumber += 1) {
      cells.push(normalizeCellString(row.getCell(columnNumber).value));
    }

    rows.push(cells);
  }

  assertTabularMatrixWithinLimits(rows, "skill matrix import");
  const headers = (rows[0] ?? []).map((header) => header.trim());
  assertHeaderRow(headers, "skill matrix import");
  if (headers.length === 0) {
    return [];
  }

  return rows
    .slice(1)
    .filter((row) => row.some((value) => value.trim() !== ""))
    .map((row) =>
      headers.reduce<Record<string, string>>((record, header, index) => {
        record[header] = row[index] ?? "";
        return record;
      }, {}),
    );
}

export interface ParsedEmployeeInfo {
  displayName?: string;
  areaOfExpertise?: string;
  yearsOfExperience?: number;
  portfolioUrl?: string;
}

export interface ParsedSkillMatrix {
  employeeInfo: ParsedEmployeeInfo;
  skills: SkillEntry[];
}

// Maps Excel proficiency (1-4) → plANARCHY proficiency (2-5)
function mapProficiency(raw: string): 1 | 2 | 3 | 4 | 5 | null {
  const n = parseInt(raw, 10);
  if (isNaN(n) || n === 0) return null;
  if (n === 1) return 2;
  if (n === 2) return 3;
  if (n === 3) return 4;
  if (n === 4) return 5;
  return null;
}

function parseEmployeeInfo(rows: Record<string, string>[]): ParsedEmployeeInfo {
  const info: ParsedEmployeeInfo = {};
  for (const row of rows) {
    const item = (row["item"] ?? "").toLowerCase().trim();
    const value = (row["property"] ?? "").trim();
    if (!value || value === "please select") continue;

    if (item.includes("full name")) {
      info.displayName = value;
    } else if (item.includes("area of expertise") || item.includes("main focus")) {
      info.areaOfExpertise = value;
    } else if (item.includes("years of experience")) {
      const n = parseFloat(value);
      if (!isNaN(n)) info.yearsOfExperience = Math.round(n);
    } else if (item.includes("portfolio") || item.includes("url") || item.includes("linkedin") || item.includes("artstation")) {
      if (value.startsWith("http")) info.portfolioUrl = value;
    }
  }
  return info;
}

function parseSkillSheet(rows: Record<string, string>[], mainSkillSet: Set<string>): SkillEntry[] {
  const skills: SkillEntry[] = [];
  let mainSkillCount = 0;

  for (const row of rows) {
    const category = (row["category"] ?? "").trim();
    const item = (row["item"] ?? "").trim();
    const property = (row["property"] ?? "").trim();
    const mainSkillRaw = (row["main skillset"] ?? "").trim();

    if (!item) continue;

    const proficiency = mapProficiency(property);
    if (proficiency === null) continue; // skip 0 / no experience

    const isMainSkillCandidate = mainSkillRaw === "1" || mainSkillRaw === "2";
    const isMainSkill = isMainSkillCandidate && mainSkillCount < 2;
    if (isMainSkill) {
      mainSkillSet.add(item);
      mainSkillCount++;
    }

    skills.push({
      skill: item,
      ...(category ? { category } : {}),
      proficiency,
      ...(isMainSkillCandidate ? { isMainSkill: true } : {}),
    });
  }

  return skills;
}

/**
 * Parse a skill matrix workbook (xlsx ArrayBuffer) into structured data.
 * Returns ParsedSkillMatrix with employeeInfo and merged skills array.
 */
export async function parseSkillMatrixWorkbook(data: ArrayBuffer): Promise<ParsedSkillMatrix> {
  const ExcelJS = await getExcelJS();
  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.load(data);

  const employeeRows = worksheetToRowObjects(workbook.getWorksheet("Employee Information"));
  const softwareRows = worksheetToRowObjects(workbook.getWorksheet("Software Skills"));
  const technicalRows = worksheetToRowObjects(workbook.getWorksheet("Technical Skillset"));

  const employeeInfo = parseEmployeeInfo(employeeRows);

  // Track main skills across both sheets (max 2 total)
  const mainSkillSet = new Set<string>();
  const softwareSkills = parseSkillSheet(softwareRows, mainSkillSet);
  const technicalSkills = parseSkillSheet(technicalRows, mainSkillSet);

  // Merge: deduplicate by skill name (prefer higher proficiency)
  const skillMap = new Map<string, SkillEntry>();
  for (const s of [...softwareSkills, ...technicalSkills]) {
    const existing = skillMap.get(s.skill);
    if (!existing || s.proficiency > existing.proficiency) {
      skillMap.set(s.skill, s);
    }
  }

  return {
    employeeInfo,
    skills: Array.from(skillMap.values()),
  };
}

/**
 * Fuzzy match an areaOfExpertise string against a list of role names.
 * Returns the best matching role name or null.
 */
export function matchRoleName(areaOfExpertise: string, roleNames: string[]): string | null {
  if (!areaOfExpertise) return null;
  const needle = areaOfExpertise.toLowerCase().trim();
  // Exact match first
  const exact = roleNames.find((r) => r.toLowerCase() === needle);
  if (exact) return exact;
  // Partial match (needle contains role name or vice versa)
  const partial = roleNames.find(
    (r) => needle.includes(r.toLowerCase()) || r.toLowerCase().includes(needle),
  );
  return partial ?? null;
}
