import { loadFormForFill } from "../services/formsApi.js";
import {
  getPermit,
  getSubmissionAnswers,
  listOpeningClosingMap,
  listSubmissions
} from "../services/permitsApi.js";
import {
  findGeneralJobType,
  findLifecycleForm,
  inferLifecycleTypeFromName,
  isGeneralJobType,
  resolveJobTypeColorFromForm
} from "./jobTypeHelpers.js";
import { resolveLifecycleFormDefinition } from "./lifecycleFormHelpers.js";
import {
  SECTION_KIND,
  excludeLifecycleSections,
  isSectionSkipable,
  resolveSectionKind,
  sortSections
} from "./sectionTypeHelpers.js";

function readAnswerValue(answers, questionId) {
  return answers[questionId] ?? answers[String(questionId)] ?? "";
}

const SECTION_MEDIA_ANSWER_LABELS = {
  "Section video upload": "video",
  "Digital signature": "signature",
  "Authorization selfie": "selfie"
};

function mapApiMediaItems(row, { preferredKind = "" } = {}) {
  if (!Array.isArray(row?.media) || !row.media.length) return [];
  return row.media.map((item) => ({
    answersId: row.answers_id,
    occurrence: item.occurrence,
    url: item.file_url,
    preferredKind: preferredKind || item.preferredKind || ""
  }));
}

export function extractSectionMedia(apiAnswers = [], questionIds = []) {
  const qidSet = new Set(
    (Array.isArray(questionIds) ? questionIds : []).map((id) => String(id))
  );
  const sectionMedia = { video: [], signature: [], selfie: [] };

  for (const row of Array.isArray(apiAnswers) ? apiAnswers : []) {
    if (!qidSet.has(String(row.question_id))) continue;
    const kind = SECTION_MEDIA_ANSWER_LABELS[String(row.answer_value || "").trim()];
    if (!kind) continue;
    sectionMedia[kind].push(
      ...mapApiMediaItems(row, { preferredKind: kind === "video" ? "video" : "image" })
    );
  }

  return sectionMedia;
}

export function buildPreviewMapsFromApiAnswers(apiAnswers = []) {
  const answers = {};
  const mediaByQuestion = {};

  for (const row of apiAnswers) {
    const qid = row.question_id;
    const value = String(row.answer_value ?? "").trim();
    const mediaItems = mapApiMediaItems(row);

    if (SECTION_MEDIA_ANSWER_LABELS[value]) {
      if (mediaItems.length) {
        mediaByQuestion[qid] = [...(mediaByQuestion[qid] || []), ...mediaItems];
        mediaByQuestion[String(qid)] = mediaByQuestion[qid];
      }
      continue;
    }

    answers[qid] = row.answer_value ?? "";
    answers[String(qid)] = answers[qid];
    if (mediaItems.length) {
      mediaByQuestion[qid] = [...(mediaByQuestion[qid] || []), ...mediaItems];
      mediaByQuestion[String(qid)] = mediaByQuestion[qid];
    }
  }

  return { answers, mediaByQuestion };
}

function resolveJobTypeName(form, jobTypes) {
  const jobTypeId = Number(form?.job_type_id);
  const jobType = (Array.isArray(jobTypes) ? jobTypes : []).find(
    (row) => Number(row.job_type_id) === jobTypeId
  );
  return jobType?.job_type_name || form?.title || "PERMIT";
}

function submissionCreatedAtMs(submission) {
  const t = new Date(submission?.created_at || 0).getTime();
  return Number.isFinite(t) ? t : Number(submission?.submission_id) || 0;
}

/**
 * Resolve submission role for preview bucketing.
 * Falls back when `submission_role` was not persisted (older API rows).
 */
export function inferSubmissionRole(submission, ctx) {
  const sid = Number(submission?.submission_id);
  const explicit = String(submission?.submission_role || "").toLowerCase();
  if (["opening", "closing", "handover", "handback", "audit"].includes(explicit)) {
    return explicit;
  }

  const {
    openingSubId,
    closingSubIds,
    formById,
    jobTypes,
    forms,
    openingFormId,
    permit,
    submissions
  } = ctx;

  if (sid === Number(openingSubId)) return "opening";
  if (closingSubIds?.has?.(sid)) return "handback";

  const formId = Number(submission?.form_id);
  const formMeta = formById[formId];

  const handoverForm = findLifecycleForm(forms, jobTypes, "handover");
  const handbackForm = findLifecycleForm(forms, jobTypes, "handback");
  const auditForm = findLifecycleForm(forms, jobTypes, "audit", ctx.formTypes || []);
  if (handoverForm && Number(handoverForm.form_id) === formId) return "handover";
  if (handbackForm && Number(handbackForm.form_id) === formId) return "handback";
  if (auditForm && Number(auditForm.form_id) === formId) return "audit";

  const jobType = (Array.isArray(jobTypes) ? jobTypes : []).find(
    (row) => Number(row.job_type_id) === Number(formMeta?.job_type_id)
  );
  if (jobType) {
    const lifecycle = inferLifecycleTypeFromName(jobType.job_type_name);
    if (lifecycle) return lifecycle;
    if (!isGeneralJobType(jobType)) return "specialized";
  }

  const titleLifecycle = inferLifecycleTypeFromName(formMeta?.title || "");
  if (titleLifecycle) return titleLifecycle;

  // Lifecycle often submits against the general/opening form when no dedicated form exists.
  if (
    openingFormId &&
    formId === Number(openingFormId) &&
    sid !== Number(openingSubId)
  ) {
    const permitClosed = Number(permit?.permit_status_id) === 3;
    if (permitClosed && Array.isArray(submissions)) {
      const siblings = submissions.filter(
        (row) =>
          Number(row.form_id) === Number(openingFormId) &&
          Number(row.submission_id) !== Number(openingSubId)
      );
      if (siblings.length) {
        const latest = [...siblings].sort(
          (a, b) => submissionCreatedAtMs(b) - submissionCreatedAtMs(a)
        )[0];
        if (Number(latest.submission_id) === sid) {
          return "handback";
        }
      }
    }
    return "handover";
  }

  return "specialized";
}

function specializedDisplayLabel(submission, form, jobTypes) {
  const jobTypeId = Number(form?.job_type_id);
  const jobType = (Array.isArray(jobTypes) ? jobTypes : []).find(
    (row) => Number(row.job_type_id) === jobTypeId
  );
  if (jobType && !isGeneralJobType(jobType)) {
    return jobType.job_type_name;
  }
  return form?.title || `Form #${submission.form_id}`;
}

export function detectSectionSkipped(section, questions, answers) {
  if (!isSectionSkipable(section)) return false;
  const firstQuestion = (questions || [])[0];
  if (!firstQuestion) return false;
  const value = String(readAnswerValue(answers, firstQuestion.question_id)).toLowerCase();
  return value.includes("skipped");
}

export function formatPreviewSubmittedAt(raw) {
  if (!raw) return "—";
  const date = new Date(raw);
  return Number.isNaN(date.getTime()) ? String(raw) : date.toLocaleString();
}

function questionsFromAnswerRows(apiAnswers = []) {
  return apiAnswers
    .filter((row) => row.question_id != null)
    .map((row) => ({
      question_id: row.question_id,
      question: row.question || `Question #${row.question_id}`,
      question_type_id: row.question_type_id,
      question_type: row.question_type
    }));
}

function buildFormSectionEntries(
  submission,
  formDefinition,
  { answers, mediaByQuestion, apiAnswers = [], sourceLabel, groupTone }
) {
  const entries = [];
  for (const section of excludeLifecycleSections(sortSections(formDefinition?.sections || []))) {
    const sectionKind = resolveSectionKind(section);
    const sectionQuestions = Array.isArray(section.questions) ? section.questions : [];
    const sectionMedia = extractSectionMedia(
      apiAnswers,
      sectionQuestions.map((question) => question.question_id)
    );
    entries.push({
      section,
      sectionKind,
      questions: sectionQuestions,
      skipped: detectSectionSkipped(section, sectionQuestions, answers),
      answers,
      mediaByQuestion,
      sectionMedia,
      sourceLabel,
      groupTone,
      submissionId: submission.submission_id,
      submittedAt: submission.created_at
    });
  }
  return entries;
}

async function buildLifecycleStepSections(
  submission,
  type,
  { submissions, forms, jobTypes, formTypes = [], questionTypeOptions, apiAnswers, answers, mediaByQuestion }
) {
  const sectionKind =
    type === "handover"
      ? SECTION_KIND.HANDOVER
      : type === "handback"
        ? SECTION_KIND.HANDBACK
        : SECTION_KIND.AUDIT;
  const label =
    type === "handover" ? "Handover" : type === "handback" ? "Handback" : "Audit";

  const fallbackQuestions = questionsFromAnswerRows(apiAnswers);
  const fallbackSectionMedia = extractSectionMedia(
    apiAnswers,
    fallbackQuestions.map((question) => question.question_id)
  );
  const fallbackSection = {
    section: {
      section_id: `lifecycle-${type}-${submission.submission_id}`,
      title: label
    },
    sectionKind,
    questions: fallbackQuestions,
    skipped: false,
    answers,
    mediaByQuestion,
    sectionMedia: fallbackSectionMedia,
    sourceLabel: label,
    groupTone: type,
    submissionId: submission.submission_id,
    submittedAt: submission.created_at
  };

  if (fallbackQuestions.length) {
    try {
      const lifecycleDef = await resolveLifecycleFormDefinition({
        type,
        submissionRows: submissions,
        forms,
        jobTypes,
        formTypes,
        questionTypeOptions
      });

      const baseSection = lifecycleDef.sections[0] || {
        section_id: `lifecycle-${type}-${submission.submission_id}`,
        title: lifecycleDef.displayTitle
      };
      const section = {
        ...baseSection,
        title: baseSection.title || lifecycleDef.displayTitle,
        questions: lifecycleDef.questions?.length
          ? lifecycleDef.questions
          : fallbackQuestions
      };

      return [
        {
          section,
          sectionKind,
          questions: section.questions,
          skipped: false,
          answers,
          mediaByQuestion,
          sectionMedia: extractSectionMedia(
            apiAnswers,
            (section.questions || []).map((question) => question.question_id)
          ),
          sourceLabel: label,
          groupTone: type,
          submissionId: submission.submission_id,
          submittedAt: submission.created_at
        }
      ];
    } catch {
      return [fallbackSection];
    }
  }

  try {
    const lifecycleDef = await resolveLifecycleFormDefinition({
      type,
      submissionRows: submissions,
      forms,
      jobTypes,
      formTypes,
      questionTypeOptions
    });

    const baseSection = lifecycleDef.sections[0] || {
      section_id: `lifecycle-${type}-${submission.submission_id}`,
      title: lifecycleDef.displayTitle
    };
    const section = {
      ...baseSection,
      title: baseSection.title || lifecycleDef.displayTitle,
      questions: lifecycleDef.questions
    };

    return [
      {
        section,
        sectionKind,
        questions: lifecycleDef.questions,
        skipped: false,
        answers,
        mediaByQuestion,
        sectionMedia: extractSectionMedia(
          apiAnswers,
          (lifecycleDef.questions || []).map((question) => question.question_id)
        ),
        sourceLabel: label,
        groupTone: type,
        submissionId: submission.submission_id,
        submittedAt: submission.created_at
      }
    ];
  } catch {
    return [];
  }
}

/**
 * Build preview pages: general + each specialized form, each including permit-level handovers and handback below the form.
 */
export function buildPreviewSteps({
  general = null,
  specialized = [],
  handovers = [],
  audits = [],
  handback = null
} = {}) {
  const lifecycleSections = [];

  const handoverTotal = handovers.length;
  handovers.forEach((entry, index) => {
    if (!entry?.sections?.length) return;
    const label =
      handoverTotal > 1 ? `Handover ${index + 1} of ${handoverTotal}` : "Handover";
    for (const sectionEntry of entry.sections) {
      lifecycleSections.push({
        ...sectionEntry,
        sourceLabel: label,
        groupTone: "handover",
        lifecycleSubmissionId: entry.submissionId,
        lifecycleSubmittedAt: entry.submittedAt
      });
    }
  });

  const auditTotal = audits.length;
  audits.forEach((entry, index) => {
    if (!entry?.sections?.length) return;
    const label = auditTotal > 1 ? `Audit ${index + 1} of ${auditTotal}` : "Audit";
    for (const sectionEntry of entry.sections) {
      lifecycleSections.push({
        ...sectionEntry,
        sourceLabel: label,
        groupTone: "audit",
        lifecycleSubmissionId: entry.submissionId,
        lifecycleSubmittedAt: entry.submittedAt
      });
    }
  });

  if (handback?.sections?.length) {
    for (const sectionEntry of handback.sections) {
      lifecycleSections.push({
        ...sectionEntry,
        sourceLabel: "Handback",
        groupTone: "handback",
        lifecycleSubmissionId: handback.submissionId,
        lifecycleSubmittedAt: handback.submittedAt
      });
    }
  }

  function withLifecycleBelow(formStep) {
    if (!formStep?.sections?.length) return null;
    return {
      ...formStep,
      sections: [...formStep.sections, ...lifecycleSections]
    };
  }

  /** @type {Array<object>} */
  const steps = [];

  const generalStep = withLifecycleBelow(general);
  if (generalStep) {
    steps.push({
      id: "general",
      type: "general",
      label: "General permit",
      groupTone: "general",
      documentTitle: general.jobTypeName || "GENERAL",
      jobColourCode: general.jobColourCode,
      submissionId: general.submissionId,
      submittedAt: general.submittedAt,
      ...generalStep
    });
  }

  specialized.forEach((entry) => {
    const specStep = withLifecycleBelow(entry);
    if (!specStep) return;
    steps.push({
      id: `specialized-${entry.submissionId}`,
      type: "specialized",
      label: entry.label,
      groupTone: "specialized",
      documentTitle: entry.jobTypeName || entry.label,
      jobColourCode: entry.jobColourCode,
      submissionId: entry.submissionId,
      submittedAt: entry.submittedAt,
      ...specStep
    });
  });

  return steps;
}

/**
 * Load permit metadata and build sequential preview steps for one permit.
 */
export async function loadPermitPreview(
  permitId,
  { forms = [], jobTypes = [], formTypes = [], questionTypeOptions = [] } = {}
) {
  const pid = Number(permitId);
  if (!Number.isFinite(pid) || pid <= 0) {
    throw new Error("Invalid permit id.");
  }

  const [permit, submissionRows, mappingRows] = await Promise.all([
    getPermit(pid),
    listSubmissions({ permit_id: pid }),
    listOpeningClosingMap({ permit_id: pid })
  ]);

  if (!permit) {
    throw new Error("Permit not found.");
  }

  const submissions = Array.isArray(submissionRows) ? submissionRows : [];
  if (!submissions.length) {
    throw new Error("This permit has no submitted forms yet.");
  }

  const mapForPermit = (Array.isArray(mappingRows) ? mappingRows : []).find(
    (row) => Number(row.permit_id) === pid
  );
  const openingSubId =
    mapForPermit?.opening_sub_id ??
    submissions.find((row) => String(row.submission_role || "").toLowerCase() === "opening")
      ?.submission_id ??
    null;

  const formById = Object.fromEntries(
    (Array.isArray(forms) ? forms : []).map((form) => [Number(form.form_id), form])
  );

  const openingSubmission =
    submissions.find((row) => Number(row.submission_id) === Number(openingSubId)) ||
    submissions.find((row) => String(row.submission_role || "").toLowerCase() === "opening") ||
    null;

  const openingFormId = openingSubmission ? Number(openingSubmission.form_id) : null;

  const closingSubIds = new Set(
    (Array.isArray(mappingRows) ? mappingRows : [])
      .map((row) => Number(row.closing_sub_id))
      .filter((id) => Number.isFinite(id) && id > 0)
  );

  const roleCtx = {
    openingSubId,
    closingSubIds,
    formById,
    jobTypes,
    forms,
    formTypes,
    openingFormId,
    permit,
    submissions
  };

  const submissionsWithRole = submissions.map((row) => ({
    row,
    role: inferSubmissionRole(row, roleCtx)
  }));

  const specializedSubmissions = submissionsWithRole
    .filter(({ role }) => role === "specialized")
    .map(({ row }) => row)
    .sort((a, b) => submissionCreatedAtMs(a) - submissionCreatedAtMs(b));

  const handoverSubmissions = submissionsWithRole
    .filter(({ role }) => role === "handover")
    .map(({ row }) => row)
    .sort((a, b) => submissionCreatedAtMs(a) - submissionCreatedAtMs(b));

  const handbackSubmissions = submissionsWithRole
    .filter(({ role }) => role === "handback")
    .map(({ row }) => row)
    .sort((a, b) => submissionCreatedAtMs(a) - submissionCreatedAtMs(b));

  const auditSubmissions = submissionsWithRole
    .filter(({ role }) => role === "audit")
    .map(({ row }) => row)
    .sort((a, b) => submissionCreatedAtMs(a) - submissionCreatedAtMs(b));

  /** @type {object|null} */
  let general = null;
  /** @type {Array<object>} */
  const specialized = [];
  /** @type {Array<object>} */
  const handovers = [];
  /** @type {Array<object>} */
  const audits = [];
  /** @type {object|null} */
  let handback = null;

  if (openingSubmission) {
    const formId = Number(openingSubmission.form_id);
    const formMeta = formById[formId] || null;
    const answerPayload = await getSubmissionAnswers(openingSubmission.submission_id);
    const apiAnswers = answerPayload?.answers || [];
    const { answers, mediaByQuestion } = buildPreviewMapsFromApiAnswers(apiAnswers);

    if (Number.isFinite(formId) && formId > 0) {
      const formDefinition = await loadFormForFill(formId);
      const sections = buildFormSectionEntries(openingSubmission, formDefinition, {
        answers,
        mediaByQuestion,
        apiAnswers,
        sourceLabel: "General permit",
        groupTone: "general"
      });

      if (sections.length) {
        general = {
          submissionId: openingSubmission.submission_id,
          submittedAt: openingSubmission.created_at,
          jobTypeName: resolveJobTypeName(formMeta, jobTypes),
          jobColourCode: resolveJobTypeColorFromForm(formMeta, jobTypes),
          sections
        };
      }
    }
  }

  for (const submission of specializedSubmissions) {
    const formId = Number(submission.form_id);
    if (!Number.isFinite(formId) || formId <= 0) continue;

    const formMeta = formById[formId] || null;
    const answerPayload = await getSubmissionAnswers(submission.submission_id);
    const apiAnswers = answerPayload?.answers || [];
    const { answers, mediaByQuestion } = buildPreviewMapsFromApiAnswers(apiAnswers);
    const formDefinition = await loadFormForFill(formId);
    const label = specializedDisplayLabel(submission, formMeta, jobTypes);
    const sections = buildFormSectionEntries(submission, formDefinition, {
      answers,
      mediaByQuestion,
      apiAnswers,
      sourceLabel: label,
      groupTone: "specialized"
    });

    if (sections.length) {
      specialized.push({
        submissionId: submission.submission_id,
        submittedAt: submission.created_at,
        label,
        jobTypeName: resolveJobTypeName(formMeta, jobTypes),
        jobColourCode: resolveJobTypeColorFromForm(formMeta, jobTypes),
        sections
      });
    }
  }

  for (const submission of handoverSubmissions) {
    const answerPayload = await getSubmissionAnswers(submission.submission_id);
    const apiAnswers = answerPayload?.answers || [];
    const { answers, mediaByQuestion } = buildPreviewMapsFromApiAnswers(apiAnswers);
    const sections = await buildLifecycleStepSections(submission, "handover", {
      submissions,
      forms,
      jobTypes,
      formTypes,
      questionTypeOptions,
      apiAnswers,
      answers,
      mediaByQuestion
    });

    if (sections.length) {
      handovers.push({
        submissionId: submission.submission_id,
        submittedAt: submission.created_at,
        sections
      });
    }
  }

  for (const submission of auditSubmissions) {
    const answerPayload = await getSubmissionAnswers(submission.submission_id);
    const apiAnswers = answerPayload?.answers || [];
    const { answers, mediaByQuestion } = buildPreviewMapsFromApiAnswers(apiAnswers);
    const sections = await buildLifecycleStepSections(submission, "audit", {
      submissions,
      forms,
      jobTypes,
      formTypes,
      questionTypeOptions,
      apiAnswers,
      answers,
      mediaByQuestion
    });

    if (sections.length) {
      audits.push({
        submissionId: submission.submission_id,
        submittedAt: submission.created_at,
        sections
      });
    }
  }

  if (handbackSubmissions.length) {
    const submission = handbackSubmissions[0];
    const answerPayload = await getSubmissionAnswers(submission.submission_id);
    const apiAnswers = answerPayload?.answers || [];
    const { answers, mediaByQuestion } = buildPreviewMapsFromApiAnswers(apiAnswers);
    const sections = await buildLifecycleStepSections(submission, "handback", {
      submissions,
      forms,
      jobTypes,
      formTypes,
      questionTypeOptions,
      apiAnswers,
      answers,
      mediaByQuestion
    });

    if (sections.length) {
      handback = {
        submissionId: submission.submission_id,
        submittedAt: submission.created_at,
        sections
      };
    }
  }

  const generalJobType = findGeneralJobType(jobTypes);
  const openingFormMeta = openingSubmission
    ? formById[Number(openingSubmission.form_id)] || null
    : null;

  const steps = buildPreviewSteps({ general, specialized, handovers, audits, handback });

  if (!steps.length) {
    throw new Error("No preview content found for this permit.");
  }

  const sectionCount = steps.reduce((sum, step) => sum + (step.sections?.length || 0), 0);

  return {
    permit,
    openingSubId,
    generalJobTypeName: generalJobType?.job_type_name || "General",
    jobTypeName: resolveJobTypeName(openingFormMeta, jobTypes),
    steps,
    sectionCount,
    submissionCount: submissions.length
  };
}

/** @deprecated Use preview.steps — flat list kept for compatibility. */
export function flattenPreviewSteps(steps = []) {
  return steps.flatMap((step) => step.sections || []);
}
