import { ARCHETYPE_IDS, type Answer, type ArchetypeId, type Question } from './types';

/** Likert answers score 0–4 (answer minus one). A scenario pick is worth 3 points. */
export const LIKERT_MAX_POINTS = 4;
export const SCENARIO_POINTS = 3;
/** Top two archetypes this close (in points) are reported as a blended profile. */
export const BLEND_MARGIN = 2;

export type Band = 'lead' | 'supporting' | 'strong' | 'situational' | 'complement';

export interface ArchetypeScore {
  id: ArchetypeId;
  rank: number;
  points: number;
  maxPoints: number;
  likertPoints: number;
  scenarioPicks: number;
  /** 0–100, relative to the strongest archetype in this profile. */
  relative: number;
  band: Band;
}

export interface Profile {
  /** Strongest first. */
  scores: ArchetypeScore[];
  lead: ArchetypeId;
  supporting: ArchetypeId;
  isBlended: boolean;
}

export function scoreProfile(questions: Question[], answers: Answer[]): Profile {
  if (answers.length !== questions.length) {
    throw new Error(`Expected ${questions.length} answers, received ${answers.length}`);
  }

  const tally = new Map(
    ARCHETYPE_IDS.map((id) => [id, { likertPoints: 0, scenarioPicks: 0, maxPoints: 0 }]),
  );

  questions.forEach((question, i) => {
    const answer = answers[i];
    if (question.kind === 'likert') {
      if (typeof answer !== 'number' || answer < 1 || answer > 5) {
        throw new Error(`Invalid answer for ${question.id}`);
      }
      const t = tally.get(question.archetype)!;
      t.maxPoints += LIKERT_MAX_POINTS;
      t.likertPoints += answer - 1;
    } else {
      if (typeof answer !== 'string' || !question.options.some((o) => o.archetype === answer)) {
        throw new Error(`Invalid answer for ${question.id}`);
      }
      for (const option of question.options) tally.get(option.archetype)!.maxPoints += SCENARIO_POINTS;
      tally.get(answer)!.scenarioPicks += 1;
    }
  });

  // Ties go to the archetype chosen more often in forced-choice scenarios (the harder signal
  // to inflate), then to self-rating, then to the fixed archetype order so results are stable.
  const ranked = ARCHETYPE_IDS.map((id, order) => {
    const t = tally.get(id)!;
    return { id, order, ...t, points: t.likertPoints + t.scenarioPicks * SCENARIO_POINTS };
  }).sort(
    (a, b) =>
      b.points - a.points ||
      b.scenarioPicks - a.scenarioPicks ||
      b.likertPoints - a.likertPoints ||
      a.order - b.order,
  );

  const top = Math.max(ranked[0].points, 1);
  const scores: ArchetypeScore[] = ranked.map(({ order: _order, ...s }, rank) => {
    const relative = Math.round((s.points / top) * 100);
    return { ...s, rank, relative, band: bandFor(rank, relative) };
  });

  return {
    scores,
    lead: scores[0].id,
    supporting: scores[1].id,
    isBlended: scores[0].points - scores[1].points <= BLEND_MARGIN,
  };
}

function bandFor(rank: number, relative: number): Band {
  if (rank === 0) return 'lead';
  if (rank === 1) return 'supporting';
  if (relative >= 75) return 'strong';
  if (relative >= 50) return 'situational';
  return 'complement';
}

export function scoreFor(profile: Profile, id: ArchetypeId): ArchetypeScore {
  return profile.scores.find((s) => s.id === id)!;
}

/** The lowest-scoring archetypes: where a partner or hire will likely add the most. */
export function weakest(profile: Profile, count = 3): ArchetypeScore[] {
  return profile.scores.slice(-count).reverse();
}

/** Of the lead archetype's two named complements, the one this person scores lowest on. */
export function priorityComplement(profile: Profile, complements: readonly ArchetypeId[]): ArchetypeId {
  return [...complements].sort((a, b) => scoreFor(profile, a).points - scoreFor(profile, b).points)[0];
}
