import type {
  AboutPage,
  Blog,
  BlogPosting,
  BreadcrumbList,
  ContactPage,
  Event,
  FAQPage,
  Graph,
  ImageGallery,
  ImageObject,
  Organization,
  WebPage,
  WebSite,
  WithContext,
} from "schema-dts";
import type { Locale } from "next-intl";
import { site } from "../site";
import { absolutePageUrl, type Href } from "./meta";

/**
 * Profiles the company record actually vouches for. `sameAs` is an identity claim,
 * so only URLs held in `site.social` as URLs belong here.
 *
 * No X/Twitter entry on purpose: `site.social.twitter` is the `@handle` consumed by
 * `twitter:site`, and the official company record lists LinkedIn, Instagram and
 * Facebook only — deriving `https://x.com/<handle>` from it would assert a profile
 * nobody has verified exists.
 */
const orgSameAs = [
  site.social.github,
  site.social.linkedin,
  site.social.instagram,
  site.social.facebook,
].filter((url) => url.length > 0);

/**
 * Full PostalAddress for the Bangkok factory, built from `site.contact`.
 * Only emits fields that are actually known (no fabrication).
 */
const postalAddress = {
  "@type": "PostalAddress" as const,
  ...(site.contact.streetAddress ? { streetAddress: site.contact.streetAddress } : {}),
  ...(site.contact.addressLocality ? { addressLocality: site.contact.addressLocality } : {}),
  ...(site.contact.postalCode ? { postalCode: site.contact.postalCode } : {}),
  ...(site.contact.addressCountry ? { addressCountry: site.contact.addressCountry } : {}),
};

/**
 * The Bangkok plant as a `Place`. A plain `Organization` has no `geo` property —
 * only a Place does — so the coordinates that used to hang off the LocalBusiness
 * node are attached through `location` instead of being dropped.
 */
const factoryLocation = {
  "@type": "Place" as const,
  name: site.legalName,
  address: postalAddress,
  ...(site.contact.geo
    ? {
        geo: {
          "@type": "GeoCoordinates" as const,
          latitude: site.contact.geo.latitude,
          longitude: site.contact.geo.longitude,
        },
      }
    : {}),
};

/** Markets the company states it ships to — shared by the entity and its contact point. */
const areaServed = ["Europe", "United Kingdom", "United States", "Asia"] as const;

/** Certifying body of the RJC membership the home page displays as a certificate. */
const rjcCredential = {
  "@type": "EducationalOccupationalCredential" as const,
  name: "Responsible Jewellery Council Certified Member",
  recognizedBy: {
    "@type": "Organization" as const,
    name: "Responsible Jewellery Council",
    url: "https://www.responsiblejewellery.com/",
  },
};

/**
 * The company node, referenced as `{ "@id": "#organization" }` from every other node.
 *
 * `Organization`, deliberately not `JewelryStore`: that one is a LocalBusiness subtype
 * describing a shop that sells jewellery to the public, which a B2B manufacturer is not
 * — and it made the entity eligible for local-commerce treatment. The physical-site
 * signals are all kept, the coordinates through `location` (see above).
 *
 * The properties below are the ones the page itself already claims in prose — founding
 * year, headcount, RJC membership, markets served — restated in a machine-readable form.
 * Anything the company record does not state is simply not emitted.
 */
const organizationNode: Organization = {
  "@type": "Organization",
  "@id": `${site.url}#organization`,
  // The trading name — that is what should surface in a brand search / knowledge
  // panel. The short name and the legal name follow as aliases. The `ADV MFR`
  // wordmark is deliberately absent: it is a logo lockup, not a name.
  name: site.name,
  alternateName: [site.shortName, site.legalName],
  legalName: site.legalName,
  url: site.url,
  // Raster + explicit dimensions: Google needs a crawlable logo it can measure, and a
  // bare SVG URL leaves the >= 112px requirement unverifiable. Dimensions are Distance
  // (a string) in schema.org, hence the interpolation rather than a raw number.
  logo: {
    "@type": "ImageObject",
    url: `${site.url}${site.logoRaster.src}`,
    width: `${site.logoRaster.width}`,
    height: `${site.logoRaster.height}`,
  },
  foundingDate: "1989",
  numberOfEmployees: { "@type": "QuantitativeValue", minValue: 400 },
  hasCredential: rjcCredential,
  areaServed: [...areaServed],
  email: site.contact.email,
  ...(site.contact.phone ? { telephone: site.contact.phone } : {}),
  ...(site.contact.fax ? { faxNumber: site.contact.fax } : {}),
  // Flat `email`/`telephone` say how to reach the company; the ContactPoint says who
  // answers, in which languages, for which markets — the shape Google reads for the
  // contact block. Both point at the single set of details held in site.ts.
  contactPoint: {
    "@type": "ContactPoint",
    contactType: "sales",
    email: site.contact.email,
    ...(site.contact.phone ? { telephone: site.contact.phone } : {}),
    availableLanguage: [...site.locales],
    areaServed: [...areaServed],
  },
  // Thai company registration number — doubles as the corporate tax ID.
  ...(site.registrationNumber ? { taxID: site.registrationNumber } : {}),
  address: postalAddress,
  location: factoryLocation,
  ...(orgSameAs.length > 0 ? { sameAs: orgSameAs } : {}),
};

const websiteNode: WebSite = {
  "@id": `${site.url}#website`,
  "@type": "WebSite",
  // Google derives the site name printed above a SERP result from this field, so it
  // carries the trading name — the same one `og:site_name` and `Organization.name`
  // declare. The other spellings a user may search for follow as aliases: the short
  // name, and the suffix every `<title>` ends with.
  name: site.name,
  alternateName: [site.shortName, site.titleSuffix],
  url: site.url,
  inLanguage: [...site.locales],
  publisher: { "@id": `${site.url}#organization` },
};

export type Crumb = { name: string; path: string };

/**
 * Shared argument shape of every page-level node.
 *
 * Takes the typed route `href` plus the current `locale` rather than a raw path: the
 * URL is then built by `absolutePageUrl` — the very helper the canonical and the
 * hreflang alternates go through — so an FR page can no longer advertise the
 * unprefixed EN URL while its canonical points at `/fr`. `inLanguage` comes from the
 * same `locale`, which is also what `<html lang>` and the WebSite node declare.
 */
type LocalisedPageArgs = {
  name: string;
  description: string;
  href: Href;
  locale: Locale;
};

export function breadcrumbJsonLd(crumbs: Crumb[]): WithContext<BreadcrumbList> {
  return {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    itemListElement: crumbs.map((crumb, index) => ({
      "@type": "ListItem",
      position: index + 1,
      name: crumb.name,
      item: `${site.url}${crumb.path === "/" ? "" : crumb.path}`,
    })),
  };
}

export function blogJsonLd(
  args: LocalisedPageArgs & {
    posts: { headline: string; url: string; datePublished: string }[];
  },
): WithContext<Blog> {
  return {
    "@context": "https://schema.org",
    "@type": "Blog",
    name: args.name,
    description: args.description,
    url: absolutePageUrl(args),
    inLanguage: args.locale,
    isPartOf: { "@id": `${site.url}#website` },
    publisher: { "@id": `${site.url}#organization` },
    blogPost: args.posts.map((p) => ({
      "@type": "BlogPosting",
      headline: p.headline,
      url: p.url,
      datePublished: p.datePublished,
    })),
  };
}

/**
 * `primaryImage` accepts a `next/image` static import as-is, so the dimensions come
 * from the file rather than from a hand-copied pair of numbers.
 */
type WebPageArgs = LocalisedPageArgs & {
  primaryImage?: { src: string; width: number; height: number };
};

function webPageNode(args: WebPageArgs): WebPage {
  return {
    "@type": "WebPage",
    name: args.name,
    description: args.description,
    url: absolutePageUrl(args),
    // Matches `<html lang>` and the `inLanguage` already declared by the WebSite node.
    inLanguage: args.locale,
    isPartOf: { "@id": `${site.url}#website` },
    about: { "@id": `${site.url}#organization` },
    ...(args.primaryImage
      ? {
          primaryImageOfPage: {
            "@type": "ImageObject" as const,
            url: `${site.url}${args.primaryImage.src}`,
            width: `${args.primaryImage.width}`,
            height: `${args.primaryImage.height}`,
          },
        }
      : {}),
  };
}

export function webPageJsonLd(args: WebPageArgs): WithContext<WebPage> {
  return { "@context": "https://schema.org", ...webPageNode(args) };
}

/**
 * Home page structured data as one `@graph` emitted once, instead of three sibling
 * `<script>` blocks each repeating `@context`. The nodes cross-reference by `@id`,
 * so a single graph is what parsers expect to resolve them against each other.
 */
export function homeJsonLd(args: WebPageArgs): Graph {
  return {
    "@context": "https://schema.org",
    "@graph": [organizationNode, websiteNode, webPageNode(args)],
  };
}

export function aboutPageJsonLd(args: LocalisedPageArgs): WithContext<AboutPage> {
  return {
    "@context": "https://schema.org",
    "@type": "AboutPage",
    name: args.name,
    description: args.description,
    url: absolutePageUrl(args),
    isPartOf: { "@id": `${site.url}#website` },
    inLanguage: args.locale,
    about: { "@id": `${site.url}#organization` },
  };
}

export function contactPageJsonLd(args: LocalisedPageArgs): WithContext<ContactPage> {
  return {
    "@context": "https://schema.org",
    "@type": "ContactPage",
    name: args.name,
    description: args.description,
    url: absolutePageUrl(args),
    isPartOf: { "@id": `${site.url}#website` },
    inLanguage: args.locale,
    about: { "@id": `${site.url}#organization` },
    mainEntity: {
      "@type": "Organization",
      "@id": `${site.url}#organization`,
      email: site.contact.email,
      ...(site.contact.phone ? { telephone: site.contact.phone } : {}),
      address: postalAddress,
    },
  };
}

export function imageGalleryJsonLd(
  args: LocalisedPageArgs & { images: { url: string; caption?: string }[] },
): WithContext<ImageGallery> {
  return {
    "@context": "https://schema.org",
    "@type": "ImageGallery",
    name: args.name,
    description: args.description,
    url: absolutePageUrl(args),
    isPartOf: { "@id": `${site.url}#website` },
    inLanguage: args.locale,
    image: args.images.map(
      (img): ImageObject => ({
        "@type": "ImageObject",
        contentUrl: img.url.startsWith("http") ? img.url : `${site.url}${img.url}`,
        ...(img.caption ? { caption: img.caption } : {}),
      }),
    ),
  };
}

export function blogPostingJsonLd(args: {
  headline: string;
  description: string;
  url: string; // absolute
  inLanguage: string;
  datePublished: string;
  dateModified?: string;
  image?: string; // absolute
  authorName: string;
  section: string; // category label
}): WithContext<BlogPosting> {
  return {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    headline: args.headline,
    description: args.description,
    url: args.url,
    mainEntityOfPage: args.url,
    inLanguage: args.inLanguage,
    datePublished: args.datePublished,
    ...(args.dateModified ? { dateModified: args.dateModified } : {}),
    ...(args.image ? { image: args.image } : {}),
    articleSection: args.section,
    author: { "@type": "Organization", name: args.authorName },
    publisher: { "@id": `${site.url}#organization` },
  };
}

export function faqPageJsonLd(args: {
  questions: { question: string; answer: string }[];
}): WithContext<FAQPage> {
  return {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    mainEntity: args.questions.map((q) => ({
      "@type": "Question",
      name: q.question,
      acceptedAnswer: {
        "@type": "Answer",
        text: q.answer,
      },
    })),
  };
}

export function eventJsonLd(args: {
  name: string;
  description: string;
  startDate: string;
  endDate?: string;
  location?: string;
}): WithContext<Event> {
  return {
    "@context": "https://schema.org",
    "@type": "Event",
    name: args.name,
    description: args.description,
    startDate: args.startDate,
    ...(args.endDate ? { endDate: args.endDate } : {}),
    ...(args.location ? { location: { "@type": "Place", name: args.location } } : {}),
    organizer: { "@id": `${site.url}#organization` },
  };
}
