import { SECTION_KIND, resolveSectionKind } from "./sectionTypeHelpers.js";
import { isRiskAssessmentQuestionType } from "./questionTypeHelpers.js";

/** Maximum rows a user can add (also caps template parsing). */
export const RISK_ASSESSMENT_MAX_ROWS = 9;

/** @deprecated Use RISK_ASSESSMENT_MAX_ROWS */
export const RISK_ASSESSMENT_ROW_COUNT = RISK_ASSESSMENT_MAX_ROWS;

export const RISK_ASSESSMENT_COLUMN_HEADERS = [
  "STEPS",
  "WHAT COULD GO WRONG? (MACHINE, EQUIPMENT, PEOPLE, ORGANISATION, ENVIRONMENT)",
  "HOW COULD IT HURT SOMEBODY? (INCLUDING WHO CAN BE HARMED) - WITHOUT ANY CONTROLS.",
  "CONTROLS (COLLECTIVE PROTECTION, PPE, INSTRUCTION, TRAINING, VISUALISATION, OTHERS)",
  "IMPLEMENTED?"
];

const COLUMN_SLOTS = ["steps", "whatCouldGoWrong", "howCouldItHurt", "controls", "implemented"];

/** Rows shown when the form first opens. */
export const RISK_ASSESSMENT_INITIAL_ROWS = 1;

export const RISK_ASSESSMENT_DEFAULT_TITLE = "RISK ASSESSMENT";

export { COLUMN_SLOTS as RISK_ASSESSMENT_COLUMN_SLOTS };

/** Stable question id from API / catalog shapes. */
export function riskQuestionId(question) {
  if (!question || typeof question !== "object") return null;
  const raw = question.question_id ?? question.id;
  if (raw == null || raw === "") return null;
  return raw;
}

function normalizeLabel(value) {
  return String(value || "")
    .trim()
    .toLowerCase()
    .replace(/\s+/g, " ")
    .replace(/:$/, "");
}

function classifyRiskColumn(question) {
  const n = normalizeLabel(question?.question);
  if (!n) return null;

  if (n === "steps" || /^(step|steps)\b/.test(n)) {
    return "steps";
  }
  if (n.includes("what could go wrong") || n.includes("go wrong")) {
    return "whatCouldGoWrong";
  }
  if (n.includes("how could it hurt") || n.includes("hurt somebody")) {
    return "howCouldItHurt";
  }
  if (
    n === "controls" ||
    /^(control|controls)\b/.test(n) ||
    (n.includes("controls") && n.includes("collective protection"))
  ) {
    return "controls";
  }
  if (n.includes("implemented")) {
    return "implemented";
  }

  return null;
}

function emptyRiskRows(count = RISK_ASSESSMENT_INITIAL_ROWS) {
  return Array.from({ length: count }, () => ({
    steps: null,
    whatCouldGoWrong: null,
    howCouldItHurt: null,
    controls: null,
    implemented: null
  }));
}

function isRiskAssessmentCatalogQuestion(question) {
  return (
    isRiskAssessmentQuestionType(
      question?.question_type_id,
      question?.question_type
        ? [{ value: String(question.question_type_id ?? ""), label: question.question_type }]
        : [],
      question?.question_type
    ) || Boolean(classifyRiskColumn(question))
  );
}

/** True when this section should render the 5-column risk assessment table. */
export function shouldUseRiskAssessmentSectionLayout(section, questions = []) {
  const list = Array.isArray(questions) ? questions : [];

  if (resolveSectionKind(section) === SECTION_KIND.RISK) {
    return true;
  }

  return list.some(isRiskAssessmentCatalogQuestion);
}

function parseGroupedCatalogQuestions(questions) {
  const list = Array.isArray(questions) ? questions : [];
  const buckets = {
    steps: [],
    whatCouldGoWrong: [],
    howCouldItHurt: [],
    controls: [],
    implemented: []
  };

  for (const question of list) {
    const slot = classifyRiskColumn(question);
    if (slot) {
      buckets[slot].push(question);
    }
  }

  for (const slot of COLUMN_SLOTS) {
    buckets[slot].sort(
      (a, b) => Number(riskQuestionId(a) || 0) - Number(riskQuestionId(b) || 0)
    );
  }

  const hasGrouped =
    buckets.steps.length > 0 ||
    buckets.whatCouldGoWrong.length > 0 ||
    buckets.howCouldItHurt.length > 0 ||
    buckets.controls.length > 0 ||
    buckets.implemented.length > 0;

  if (!hasGrouped) {
    return null;
  }

  const dataRowCount = Math.max(
    ...COLUMN_SLOTS.map((slot) => buckets[slot].length),
    0
  );
  const rowCount = Math.min(
    RISK_ASSESSMENT_MAX_ROWS,
    Math.max(RISK_ASSESSMENT_INITIAL_ROWS, dataRowCount)
  );
  const rows = emptyRiskRows(rowCount);

  for (let i = 0; i < rowCount; i += 1) {
    for (const slot of COLUMN_SLOTS) {
      rows[i][slot] = buckets[slot][i] || null;
    }
  }

  return { rows };
}

function parseSequentialRiskQuestions(questions) {
  const list = Array.isArray(questions)
    ? [...questions].sort(
        (a, b) => Number(riskQuestionId(a) || 0) - Number(riskQuestionId(b) || 0)
      )
    : [];

  const riskOnly = list.filter(isRiskAssessmentCatalogQuestion);
  const source = riskOnly.length ? riskOnly : list;
  if (!source.length) {
    return { rows: emptyRiskRows(RISK_ASSESSMENT_INITIAL_ROWS) };
  }

  const rowCount = Math.min(
    RISK_ASSESSMENT_MAX_ROWS,
    Math.max(RISK_ASSESSMENT_INITIAL_ROWS, Math.ceil(source.length / COLUMN_SLOTS.length))
  );
  const rows = emptyRiskRows(rowCount);

  let index = 0;
  for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
    for (const slot of COLUMN_SLOTS) {
      if (index < source.length) {
        rows[rowIndex][slot] = source[index];
        index += 1;
      }
    }
  }

  return { rows };
}

function groupingHasHoles(rows = []) {
  for (const row of rows) {
    let filled = 0;
    for (const slot of COLUMN_SLOTS) {
      if (riskQuestionId(row[slot]) != null) filled += 1;
    }
    if (filled > 0 && filled < COLUMN_SLOTS.length) {
      return true;
    }
  }
  return false;
}

function isCompleteColumnGrouping(grouped, questions = []) {
  if (!grouped?.rows?.length) return false;
  if (groupingHasHoles(grouped.rows)) return false;

  const placed = new Set();
  for (const row of grouped.rows) {
    for (const slot of COLUMN_SLOTS) {
      const id = riskQuestionId(row[slot]);
      if (id != null) placed.add(String(id));
    }
  }

  const catalog = (Array.isArray(questions) ? questions : []).filter(
    isRiskAssessmentCatalogQuestion
  );
  const expected = catalog.length ? catalog : questions;
  const expectedIds = expected
    .map((q) => riskQuestionId(q))
    .filter((id) => id != null)
    .map(String);

  if (expectedIds.some((id) => !placed.has(id))) {
    return false;
  }

  return placed.size > 0;
}

/**
 * Pad to MAX_ROWS and reuse column questions so every cell can accept text.
 * When one question backs multiple rows, cells get `_riskRowIndex` and answers
 * are stored as a JSON string array on that question.
 */
function expandRiskRowsToMax(baseRows = []) {
  const rows = emptyRiskRows(RISK_ASSESSMENT_MAX_ROWS);
  const templates = {};

  for (let i = 0; i < baseRows.length; i += 1) {
    for (const slot of COLUMN_SLOTS) {
      const question = baseRows[i]?.[slot];
      if (riskQuestionId(question) == null) continue;
      rows[i][slot] = question;
      if (!templates[slot]) {
        templates[slot] = question;
      }
    }
  }

  for (const slot of COLUMN_SLOTS) {
    const template = templates[slot];
    if (!template) continue;

    for (let i = 0; i < RISK_ASSESSMENT_MAX_ROWS; i += 1) {
      if (riskQuestionId(rows[i][slot]) == null) {
        rows[i][slot] = template;
      }
    }

    const idCounts = {};
    for (let i = 0; i < RISK_ASSESSMENT_MAX_ROWS; i += 1) {
      const id = String(riskQuestionId(rows[i][slot]));
      idCounts[id] = (idCounts[id] || 0) + 1;
    }

    for (let i = 0; i < RISK_ASSESSMENT_MAX_ROWS; i += 1) {
      const question = rows[i][slot];
      const id = String(riskQuestionId(question));
      if (idCounts[id] > 1) {
        rows[i][slot] = { ...question, _riskRowIndex: i };
      }
    }
  }

  return rows;
}

/**
 * Map section questions to risk assessment table rows (5 columns, always 9 slots).
 * Supports repeated column labels or sequential RiskAssessment types.
 */
export function parseRiskAssessmentQuestions(questions = []) {
  const list = Array.isArray(questions) ? questions : [];
  const grouped = parseGroupedCatalogQuestions(list);
  const base =
    grouped && isCompleteColumnGrouping(grouped, list)
      ? grouped.rows
      : parseSequentialRiskQuestions(list).rows;
  return { rows: expandRiskRowsToMax(base) };
}

function parseRiskAnswerArray(raw) {
  if (raw == null || raw === "") return null;
  const str = String(raw);
  if (!str.startsWith("[")) return null;
  try {
    const arr = JSON.parse(str);
    return Array.isArray(arr) ? arr.map((v) => String(v ?? "")) : null;
  } catch {
    return null;
  }
}

function readStoredRiskValue(raw, rowIndex) {
  if (raw == null || raw === "") return "";
  const arr = parseRiskAnswerArray(raw);
  if (arr) {
    return String(arr[rowIndex] ?? "");
  }
  return rowIndex === 0 ? String(raw) : "";
}

export function readRiskAnswer(answers, question) {
  const qid = riskQuestionId(question);
  if (qid == null) return "";
  const raw = answers?.[qid] ?? answers?.[String(qid)] ?? "";
  const rowIndex = Number.isFinite(question?._riskRowIndex)
    ? question._riskRowIndex
    : null;
  if (rowIndex == null) {
    const arr = parseRiskAnswerArray(raw);
    if (arr) return String(arr[0] ?? "");
    return String(raw ?? "");
  }
  return readStoredRiskValue(raw, rowIndex);
}

/** Build the stored answer value for a cell (plain string or JSON row array). */
export function buildRiskAnswerValue(answers, question, value) {
  const qid = riskQuestionId(question);
  if (qid == null) return value;
  const rowIndex = Number.isFinite(question?._riskRowIndex)
    ? question._riskRowIndex
    : null;
  if (rowIndex == null) {
    return value;
  }

  const raw = answers?.[qid] ?? answers?.[String(qid)] ?? "";
  let arr = parseRiskAnswerArray(raw);
  if (!arr) {
    arr = raw ? [String(raw)] : [];
  }
  while (arr.length <= rowIndex) {
    arr.push("");
  }
  arr[rowIndex] = value == null ? "" : String(value);
  return JSON.stringify(arr);
}

export function rowHasAnyRiskAnswer(row, answers = {}) {
  for (const slot of COLUMN_SLOTS) {
    if (String(readRiskAnswer(answers, row[slot])).trim() !== "") {
      return true;
    }
  }
  return false;
}

/** How many rows to show (at least 1; expands through last row with data). */
export function resolveRiskVisibleRowCount(rows = [], answers = {}, explicitCount) {
  const maxRows = Math.min(RISK_ASSESSMENT_MAX_ROWS, rows.length || RISK_ASSESSMENT_MAX_ROWS);
  if (Number.isFinite(explicitCount) && explicitCount > 0) {
    return Math.min(maxRows, Math.max(RISK_ASSESSMENT_INITIAL_ROWS, explicitCount));
  }
  let lastWithData = 0;
  for (let i = 0; i < maxRows; i += 1) {
    if (rowHasAnyRiskAnswer(rows[i], answers)) {
      lastWithData = i + 1;
    }
  }
  return Math.max(RISK_ASSESSMENT_INITIAL_ROWS, lastWithData);
}

/** Questions in visible rows only (unique ids for submission). */
export function collectRiskAssessmentDisplayQuestions(
  questions = [],
  visibleRowCount = RISK_ASSESSMENT_MAX_ROWS
) {
  const { rows } = parseRiskAssessmentQuestions(questions);
  const limit = Math.min(
    rows.length,
    Math.max(RISK_ASSESSMENT_INITIAL_ROWS, visibleRowCount)
  );
  const display = [];
  const seen = new Set();

  for (let rowIndex = 0; rowIndex < limit; rowIndex += 1) {
    const row = rows[rowIndex];
    for (const slot of COLUMN_SLOTS) {
      const question = row[slot];
      const id = riskQuestionId(question);
      if (id == null) continue;
      const key = String(id);
      if (seen.has(key)) continue;
      seen.add(key);
      display.push(question);
    }
  }

  return display;
}

/** Count empty cells in visible risk rows (for validation). */
export function countEmptyRiskAssessmentCells(
  questions = [],
  answers = {},
  visibleRowCount = RISK_ASSESSMENT_INITIAL_ROWS
) {
  const { rows } = parseRiskAssessmentQuestions(questions);
  const limit = Math.min(
    rows.length,
    Math.max(RISK_ASSESSMENT_INITIAL_ROWS, visibleRowCount)
  );
  let empty = 0;

  for (let rowIndex = 0; rowIndex < limit; rowIndex += 1) {
    const row = rows[rowIndex];
    for (const slot of COLUMN_SLOTS) {
      const question = row[slot];
      if (riskQuestionId(question) == null) continue;
      if (String(readRiskAnswer(answers, question)).trim() === "") {
        empty += 1;
      }
    }
  }

  return empty;
}

/** Preview: rows with answers, or one blank row if the section is empty. */
export function getRiskAssessmentPreviewRows(questions = [], answers = {}) {
  const { rows } = parseRiskAssessmentQuestions(questions);
  let lastWithData = 0;
  for (let i = 0; i < rows.length; i += 1) {
    if (rowHasAnyRiskAnswer(rows[i], answers)) {
      lastWithData = i + 1;
    }
  }
  if (lastWithData > 0) {
    return rows.slice(0, lastWithData);
  }
  return rows.slice(0, RISK_ASSESSMENT_INITIAL_ROWS);
}

export function canAddRiskAssessmentRow(visibleRowCount) {
  return visibleRowCount < RISK_ASSESSMENT_MAX_ROWS;
}

/** Trim shared-column JSON arrays to the visible row count before submit. */
export function trimRiskAnswersForVisibleRows(
  questions = [],
  answers = {},
  visibleRowCount = RISK_ASSESSMENT_INITIAL_ROWS
) {
  const { rows } = parseRiskAssessmentQuestions(questions);
  const limit = Math.min(
    rows.length,
    Math.max(RISK_ASSESSMENT_INITIAL_ROWS, visibleRowCount)
  );
  const sharedIds = new Set();

  for (let rowIndex = 0; rowIndex < limit; rowIndex += 1) {
    const row = rows[rowIndex];
    for (const slot of COLUMN_SLOTS) {
      const question = row[slot];
      if (!Number.isFinite(question?._riskRowIndex)) continue;
      const id = riskQuestionId(question);
      if (id != null) sharedIds.add(String(id));
    }
  }

  if (!sharedIds.size) return answers;

  const next = { ...answers };
  for (const id of sharedIds) {
    const raw = next[id] ?? next[Number(id)];
    const arr = parseRiskAnswerArray(raw);
    if (!arr) continue;
    const trimmed = JSON.stringify(arr.slice(0, limit));
    if (Object.prototype.hasOwnProperty.call(next, id)) {
      next[id] = trimmed;
    } else if (Object.prototype.hasOwnProperty.call(next, Number(id))) {
      next[Number(id)] = trimmed;
    } else {
      next[id] = trimmed;
    }
  }
  return next;
}

export function riskAssessmentSectionDisplayTitle(section) {
  const raw = String(section?.title || "").trim();
  return raw || RISK_ASSESSMENT_DEFAULT_TITLE;
}
