import { and, eq, inArray } from "drizzle-orm";
import { db, people, type Office } from "@/db";

/**
 * People search + project-fit ranking.
 *
 * Two modes share one scorer:
 *  - keyword search ("react", "video", "Boston UX") for the directory
 *  - fit search: paste a pitch brief / project description and get the team
 *    whose skills, project history, client verticals and interests best match.
 *
 * Scoring is deliberately transparent so the UI can show *why* someone ranks:
 * each hit contributes to an explanation list.
 */

export interface MatchOptions {
  query: string;
  offices?: Office[];
  departments?: string[];
  limit?: number;
  /** Boost people who are currently under-allocated (fewer active projects). */
  preferAvailable?: boolean;
}

export interface MatchResult {
  person: {
    id: string;
    slug: string;
    firstName: string;
    lastName: string;
    title: string;
    department: string;
    office: Office;
    photoUrl: string | null;
    linkedinUrl: string | null;
  };
  score: number;
  reasons: string[];
  activeProjects: number;
  totalHours: number;
  matchedSkills: string[];
  matchedProjects: { name: string; client?: string; role?: string }[];
}

const STOP = new Set(
  "a an and are as at be by for from has have in into is it its of on or that the this to we with our your you will need looking want who someone team project pitch client brief new".split(" "),
);

/** Domain synonyms so "site" finds "website" people and "film" finds "video" people. */
const SYNONYMS: Record<string, string[]> = {
  website: ["web", "site", "frontend", "front-end", "next.js", "react", "wordpress", "development", "developer"],
  video: ["film", "videography", "motion", "editing", "editor", "studio", "cp studios", "production"],
  social: ["instagram", "tiktok", "community", "content", "influencer", "paid social"],
  ux: ["user experience", "product design", "prototype", "figma", "usability", "journey", "wireframe"],
  research: ["user research", "interviews", "discovery", "testing", "insight", "survey"],
  analytics: ["data", "measurement", "reporting", "dashboard", "ga4", "looker", "attribution", "cro", "experimentation"],
  brand: ["identity", "positioning", "rebrand", "strategy", "planner", "tone of voice", "packaging"],
  campaign: ["launch", "integrated", "ooh", "tv", "advertising", "creative"],
  app: ["mobile", "ios", "android", "product", "react native"],
  health: ["healthcare", "patient", "clinic", "hospital", "pharma", "medical", "wellness"],
  education: ["university", "college", "school", "enrolment", "enrollment", "student"],
  travel: ["tourism", "cruise", "airline", "hotel", "hospitality", "transport", "bus"],
  retail: ["cpg", "grocery", "shopper", "ecommerce", "e-commerce", "food", "packaging"],
  finance: ["financial", "bank", "credit union", "fintech", "insurance"],
  sports: ["sport", "rugby", "team", "stadium", "fans", "season ticket"],
};

export function tokenize(q: string): string[] {
  return q
    .toLowerCase()
    .replace(/[^a-z0-9+.#&' -]/g, " ")
    .split(/\s+/)
    .map((t) => t.replace(/^'|'$/g, ""))
    .filter((t) => t.length > 1 && !STOP.has(t));
}

/** Expand tokens with synonym groups, weighting expansions a little lower. */
function expand(tokens: string[]): Map<string, number> {
  const weights = new Map<string, number>();
  for (const t of tokens) weights.set(t, Math.max(weights.get(t) ?? 0, 1));
  for (const [head, syns] of Object.entries(SYNONYMS)) {
    const group = [head, ...syns];
    const hit = group.some((g) => tokens.some((t) => t === g || (g.length > 3 && t.includes(g)) || (t.length > 3 && g.includes(t))));
    if (hit) for (const g of group) weights.set(g, Math.max(weights.get(g) ?? 0, 0.6));
  }
  return weights;
}

function hits(text: string | null | undefined, weights: Map<string, number>): { score: number; terms: string[] } {
  if (!text) return { score: 0, terms: [] };
  const hay = text.toLowerCase();
  let score = 0;
  const terms: string[] = [];
  for (const [term, w] of weights) {
    if (hay.includes(term)) {
      score += w;
      if (w >= 1) terms.push(term);
    }
  }
  return { score, terms };
}

export async function matchPeople(opts: MatchOptions): Promise<MatchResult[]> {
  const tokens = tokenize(opts.query);
  const weights = expand(tokens);
  const limit = opts.limit ?? 12;

  const rows = await db.query.people.findMany({
    where: and(
      eq(people.isActive, true),
      opts.offices?.length ? inArray(people.office, opts.offices) : undefined,
      opts.departments?.length ? inArray(people.department, opts.departments) : undefined,
    ),
    with: {
      skills: { with: { skill: true } },
      assignments: { with: { project: { with: { client: true } } } },
    },
  });

  const results: MatchResult[] = rows.map((p) => {
    const reasons: string[] = [];
    let score = 0;
    const matchedSkills = new Set<string>();
    const matchedProjects: MatchResult["matchedProjects"] = [];

    // 1. Skills (strongest signal, weighted by proficiency)
    for (const ps of p.skills) {
      const h = hits(ps.skill.name, weights);
      if (h.score > 0) {
        score += h.score * (2 + ps.level * 0.6);
        matchedSkills.add(ps.skill.name);
      }
    }
    if (matchedSkills.size) reasons.push(`Skills: ${[...matchedSkills].slice(0, 5).join(", ")}`);

    // 2. Role / department / bio
    const roleHit = hits(`${p.title} ${p.department}`, weights);
    if (roleHit.score > 0) {
      score += roleHit.score * 2.5;
      reasons.push(`Role: ${p.title}`);
    }
    const bioHit = hits(p.bio, weights);
    score += bioHit.score * 0.8;

    // 3. Project history – weighted by hours and recency, plus client vertical
    let totalHours = 0;
    let activeProjects = 0;
    const verticalHits = new Set<string>();
    for (const asg of p.assignments) {
      totalHours += asg.hours;
      if (asg.project.status === "Active") activeProjects++;
      const projText = `${asg.project.name} ${asg.project.description ?? ""} ${asg.project.services.join(" ")} ${asg.role ?? ""} ${asg.project.client?.name ?? ""}`;
      const h = hits(projText, weights);
      const vertical = asg.project.client?.vertical;
      const vh = hits(vertical, weights);
      if (h.score > 0 || vh.score > 0) {
        const recency = asg.lastSeen ? Math.max(0.4, 1 - (Date.now() - asg.lastSeen.getTime()) / (1000 * 60 * 60 * 24 * 365 * 3)) : 0.7;
        const depth = Math.min(1.5, 0.5 + Math.log10(1 + asg.hours) / 2);
        score += (h.score * 1.5 + vh.score * 2) * recency * depth;
        matchedProjects.push({ name: asg.project.name, client: asg.project.client?.name, role: asg.role ?? undefined });
        if (vh.score > 0 && vertical) verticalHits.add(vertical);
      }
    }
    if (matchedProjects.length) reasons.push(`Worked on ${matchedProjects.length} related project${matchedProjects.length > 1 ? "s" : ""}`);
    if (verticalHits.size) reasons.push(`Vertical experience: ${[...verticalHits].join(", ")}`);

    // 4. Interests (light touch – nice for culture fit and pitch chemistry)
    const interestHit = hits(p.interests.join(" "), weights);
    if (interestHit.score > 0) {
      score += interestHit.score * 0.7;
      const matched = p.interests.filter((i) => interestHit.terms.some((t) => i.toLowerCase().includes(t)));
      if (matched.length) reasons.push(`Interests: ${matched.join(", ")}`);
    }

    // 5. Availability nudge
    if (opts.preferAvailable && score > 0) {
      score *= activeProjects === 0 ? 1.15 : activeProjects === 1 ? 1.05 : activeProjects >= 3 ? 0.85 : 1;
      if (activeProjects >= 3) reasons.push("Heavily allocated right now");
    }

    return {
      person: {
        id: p.id, slug: p.slug, firstName: p.firstName, lastName: p.lastName, title: p.title,
        department: p.department, office: p.office, photoUrl: p.photoUrl, linkedinUrl: p.linkedinUrl,
      },
      score: Math.round(score * 10) / 10,
      reasons,
      activeProjects,
      totalHours: Math.round(totalHours),
      matchedSkills: [...matchedSkills],
      matchedProjects: matchedProjects.slice(0, 4),
    };
  });

  return results
    .filter((r) => r.score > 0 || tokens.length === 0)
    .sort((a, b) => b.score - a.score || a.person.lastName.localeCompare(b.person.lastName))
    .slice(0, limit);
}

/** Suggest a balanced team: best person per discipline needed by the brief. */
export function suggestTeam(results: MatchResult[], size = 5): MatchResult[] {
  const seen = new Set<string>();
  const team: MatchResult[] = [];
  for (const r of results) {
    if (team.length >= size) break;
    if (seen.has(r.person.department)) continue;
    seen.add(r.person.department);
    team.push(r);
  }
  // Fill remaining seats with next-best regardless of department
  for (const r of results) {
    if (team.length >= size) break;
    if (!team.includes(r)) team.push(r);
  }
  return team;
}
