import type { FillerQuestion, FillerAnswerValue } from "@/types/filler";
import type { QuestionOptionsConfig, QuestionValidationConfig } from "@/types";
import { parseJsonColumn } from "@/lib/utils/parse-json-column";

/** Parse a "min-max" range string (e.g. NPS "0-6") into numeric bounds. */
function parseNumericRange(value: string): [number, number] | null {
  const match = value.match(/^(-?\d+)-(-?\d+)$/);
  if (!match) return null;
  const lo = Number(match[1]);
  const hi = Number(match[2]);
  if (Number.isNaN(lo) || Number.isNaN(hi)) return null;
  return lo <= hi ? [lo, hi] : [hi, lo];
}

function asFiniteNumber(value: unknown): number | null {
  if (typeof value === "number" && Number.isFinite(value)) return value;
  if (typeof value === "string" && value.trim() !== "") {
    const n = Number(value);
    if (Number.isFinite(n)) return n;
  }
  return null;
}

/** Evaluate whether a logic rule condition matches the given answer. */
export function ruleMatches(
  rule: FillerQuestion["logicRules"][0],
  answer: FillerAnswerValue
): boolean {
  if (rule.conditionType === "any_answer") {
    return answer !== null && answer !== undefined && answer !== "";
  }

  const conditionValue = rule.conditionValue ?? "";

  // Multi-select (checkbox) answers — "equals" means the option was among
  // those selected, "not_equals" means it was not.
  if (Array.isArray(answer)) {
    const includes = answer.some((v) => String(v) === conditionValue);
    return rule.conditionType === "equals" ? includes : !includes;
  }

  // Numeric range conditions (e.g. NPS "0-6" Detractor / "9-10" Promoter).
  // Coerce numeric strings so "9" still matches the 9–10 bucket.
  const numeric = asFiniteNumber(answer);
  const range = parseNumericRange(conditionValue);
  if (numeric != null && range) {
    const [lo, hi] = range;
    const inRange = numeric >= lo && numeric <= hi;
    return rule.conditionType === "equals" ? inRange : !inRange;
  }

  const strVal = String(answer ?? "");
  if (rule.conditionType === "equals") {
    return strVal === conditionValue;
  }
  if (rule.conditionType === "not_equals") {
    return strVal !== conditionValue;
  }
  return false;
}

/** Resolve the next question ID after answering, applying skip logic. */
export function resolveNextQuestionId(
  currentQuestion: FillerQuestion,
  answer: FillerAnswerValue,
  orderedQuestions: FillerQuestion[]
): string | null | "end" {
  const ordered = [...orderedQuestions].sort((a, b) => a.position - b.position);
  const idx = ordered.findIndex((q) => q.id === currentQuestion.id);

  for (const rule of currentQuestion.logicRules) {
    if (!ruleMatches(rule, answer)) continue;

    if (rule.action === "end_survey") return "end";

    if (rule.action === "skip_to_question" && rule.targetQuestionId) {
      const target = ordered.find((q) => q.id === rule.targetQuestionId);
      // Jump only to a later question — never loop back to self.
      if (target && target.id !== currentQuestion.id && target.position > currentQuestion.position) {
        return target.id;
      }
    }
  }

  if (idx < 0 || idx >= ordered.length - 1) return "end";
  return ordered[idx + 1].id;
}

/** Build ordered question list, optionally randomizing choices per question. */
export function prepareQuestionsForFiller(
  questions: FillerQuestion[]
): FillerQuestion[] {
  return [...questions]
    .sort((a, b) => a.position - b.position)
    .map((q) => {
      const optionsConfig = parseJsonColumn<QuestionOptionsConfig>(
        q.optionsConfig
      );
      const validationConfig = parseJsonColumn<QuestionValidationConfig>(
        q.validationConfig
      );
      const next: FillerQuestion = { ...q, optionsConfig, validationConfig };

      if (next.randomizeOptions && next.optionsConfig?.choices?.length) {
        const shuffled = [...next.optionsConfig.choices].sort(
          () => Math.random() - 0.5
        );
        return {
          ...next,
          optionsConfig: { ...next.optionsConfig, choices: shuffled },
        };
      }
      return next;
    });
}

/** Calculate filler progress percentage. */
export function calcFillerProgress(
  currentIndex: number,
  total: number
): number {
  if (total === 0) return 100;
  return Math.round(((currentIndex + 1) / total) * 100);
}

/**
 * Reconstruct the question path the respondent actually saw (respects skip/end logic).
 * Used server-side to validate only answered questions, not the full survey.
 */
export function getAnsweredQuestionPath(
  questions: FillerQuestion[],
  answerMap: Map<string, FillerAnswerValue>
): FillerQuestion[] {
  const ordered = [...questions].sort((a, b) => a.position - b.position);
  if (ordered.length === 0) return [];

  const path: FillerQuestion[] = [];
  const visited = new Set<string>();
  let currentId: string | null = ordered[0].id;

  while (currentId) {
    if (visited.has(currentId)) break;
    visited.add(currentId);

    const question = ordered.find((q) => q.id === currentId);
    if (!question) break;

    path.push(question);
    const answer = answerMap.get(question.id) ?? null;
    const next = resolveNextQuestionId(question, answer, ordered);

    if (next === "end") break;
    currentId = next;
  }

  return path;
}

/** Questions the respondent should currently see (skip logic hides the rest). */
export function getVisibleQuestions(
  questions: FillerQuestion[],
  answers: Record<string, FillerAnswerValue>
): FillerQuestion[] {
  const answerMap = new Map<string, FillerAnswerValue>(Object.entries(answers));
  return getAnsweredQuestionPath(questions, answerMap);
}

/** Drop answers for questions skip-logic has hidden so they are not submitted. */
export function pruneHiddenAnswers(
  answers: Record<string, FillerAnswerValue>,
  visibleIds: Set<string>
): Record<string, FillerAnswerValue> {
  let changed = false;
  const next: Record<string, FillerAnswerValue> = {};
  for (const [id, value] of Object.entries(answers)) {
    if (visibleIds.has(id)) {
      next[id] = value;
    } else {
      changed = true;
    }
  }
  return changed ? next : answers;
}
