import { Injectable } from '@angular/core';
import { BootstrapConfig, LocalizedHtmlContent, LocalizedTextContent, ResolvedStaticPage, StaticPageConfig, StaticPagesConfig } from '../../../shared/models/config';
import { ContentPage, ContentPageBootstrapInput } from '../models/content-page.model';
import { isValidHttpUrl, normalizeRoute, validateHtml } from '../../project-editor/schema/validators/primitives';

const KNOWN_ROBOTS_TOKENS = new Set(['index', 'noindex', 'follow', 'nofollow', 'index,follow', 'index,nofollow', 'noindex,follow', 'noindex,nofollow']);

@Injectable({ providedIn: 'root' })
export class ContentPageService {
  normalizePages(config: StaticPagesConfig | undefined): ContentPage[] {
    if (!config) {
      return [];
    }

    if (Array.isArray(config)) {
      return config.map((page, index) => {
        const slug = this.normalizeSlug(typeof page.route === 'string' ? page.route : page.route?.path ?? page.key);
        return {
          id: page.key,
          slug,
          route: slug,
          title: typeof page.title === 'string' ? page.title : page.title?.['en'] ?? page.key,
          icon: undefined,
          order: index + 1,
          showInFooter: false,
          showInHeader: false,
          showInSitemap: true,
          visibility: { desktop: true, tablet: true, mobile: true },
          requiresAuthentication: false,
          footerGroup: undefined,
          translations: this.normalizeLegacyTranslations(page.title, page.content),
          seo: undefined,
          // Legacy array-format pages predate enabled/status; they were always live, so
          // normalizing must keep them live rather than silently un-publishing them.
          enabled: page.visible !== false,
          status: 'published' as const,
        };
      });
    }

    return Object.entries(config)
      .map(([key, page], index) => this.normalizePage(page as ContentPageBootstrapInput, index, key))
      .sort((left, right) => left.order - right.order);
  }

  resolvePage(bootstrap: BootstrapConfig, keyOrSlug: string, locale: string): ResolvedStaticPage | null {
    const normalizedPages = this.normalizePages(bootstrap.staticPages);
    const target = normalizedPages.find(page => page.id === keyOrSlug || page.slug === this.normalizeSlug(keyOrSlug));
    // A disabled or draft page never resolves on the storefront, independent of
    // whether the surrounding bootstrap itself has been published.
    if (!target || !target.enabled || target.status !== 'published') {
      return null;
    }

    const translation = target.translations[locale] ?? target.translations['en'] ?? Object.values(target.translations)[0] ?? {};

    return {
      key: target.id,
      id: target.id,
      slug: target.slug,
      route: `/${this.normalizeSlug(target.route || target.slug)}`,
      title: translation.title || target.title,
      html: translation.html || '',
      icon: target.icon,
      requiresAuthentication: target.requiresAuthentication,
      seo: translation.seo ?? target.seo,
      customTemplate: target.customTemplate,
      heroImage: target.heroImage,
      thumbnail: target.thumbnail,
      gallery: target.gallery,
    };
  }

  validatePages(pages: ContentPage[]): {
    duplicateSlugs: string[];
    emptyTitles: string[];
    duplicateRoutes: string[];
    invalidHtml: string[];
    invalidSeo: string[];
  } {
    const seenSlugs = new Set<string>();
    const duplicateSlugs = new Set<string>();
    const emptyTitles = new Set<string>();
    const seenRoutes = new Set<string>();
    const duplicateRoutes = new Set<string>();
    const invalidHtml = new Set<string>();
    const invalidSeo = new Set<string>();

    for (const page of pages) {
      const slug = this.normalizeSlug(page.slug);
      if (seenSlugs.has(slug)) {
        duplicateSlugs.add(slug);
      }
      seenSlugs.add(slug);

      const route = normalizeRoute(page.route || page.slug);
      if (route) {
        if (seenRoutes.has(route)) {
          duplicateRoutes.add(route);
        }
        seenRoutes.add(route);
      }

      const hasTitle = page.title.trim().length > 0 || Object.values(page.translations).some(translation => (translation.title ?? '').trim().length > 0);
      if (!hasTitle) {
        emptyTitles.add(page.id);
      }

      const hasInvalidHtml = Object.values(page.translations).some(translation =>
        (translation.html ?? '').trim().length > 0 && !validateHtml(translation.html!).ok,
      );
      if (hasInvalidHtml) {
        invalidHtml.add(page.id);
      }

      if (this.hasInvalidSeo(page)) {
        invalidSeo.add(page.id);
      }
    }

    return {
      duplicateSlugs: [...duplicateSlugs],
      emptyTitles: [...emptyTitles],
      duplicateRoutes: [...duplicateRoutes],
      invalidHtml: [...invalidHtml],
      invalidSeo: [...invalidSeo],
    };
  }

  private hasInvalidSeo(page: ContentPage): boolean {
    const seoBlocks = [page.seo, ...Object.values(page.translations).map(translation => translation.seo)].filter(
      (seo): seo is NonNullable<typeof seo> => !!seo,
    );
    return seoBlocks.some(seo => {
      const invalidCanonical = !!seo.canonical && !isValidHttpUrl(seo.canonical);
      const invalidOgImage = !!seo.ogImage && !isValidHttpUrl(seo.ogImage);
      const invalidRobots = !!seo.robots && !KNOWN_ROBOTS_TOKENS.has(seo.robots.toLowerCase());
      return invalidCanonical || invalidOgImage || invalidRobots;
    });
  }

  toBootstrapRecord(pages: ContentPage[]): Record<string, StaticPageConfig> {
    return pages.reduce<Record<string, StaticPageConfig>>((acc, page) => {
      const htmlMap = Object.entries(page.translations).reduce<LocalizedHtmlContent>((result, [locale, translation]) => {
        if ((translation.html ?? '').trim()) {
          result[locale] = translation.html!;
        }
        return result;
      }, {});

      const titleMap = Object.entries(page.translations).reduce<LocalizedTextContent>((result, [locale, translation]) => {
        if ((translation.title ?? '').trim()) {
          result[locale] = translation.title!;
        }
        return result;
      }, {});

      const route = this.normalizeSlug(page.route || page.slug);
      acc[page.id] = {
        id: page.id,
        slug: this.normalizeSlug(page.slug),
        title: Object.keys(titleMap).length > 0 ? titleMap : page.title,
        html: htmlMap,
        showInFooter: page.showInFooter,
        showInHeader: page.showInHeader,
        showInSitemap: page.showInSitemap,
        icon: page.icon,
        order: page.order,
        visibility: page.visibility,
        requiresAuthentication: page.requiresAuthentication,
        footerGroup: page.footerGroup,
        translations: page.translations,
        seo: page.seo,
        // `visible` mirrors `enabled` so any legacy/other reader of the older
        // field name stays truthful; `enabled` is the authoritative gate.
        visible: page.enabled,
        enabled: page.enabled,
        status: page.status,
        route: `/${route}`,
        content: htmlMap,
        customTemplate: page.customTemplate,
        heroImage: page.heroImage,
        heroImageAlt: page.heroImageAlt,
        heroImageCaption: page.heroImageCaption,
        thumbnail: page.thumbnail,
        gallery: page.gallery,
        updatedAt: page.updatedAt,
      };
      return acc;
    }, {});
  }

  private normalizePage(page: ContentPageBootstrapInput, index: number, key: string): ContentPage {
    const titleMap = typeof page.title === 'object' && page.title ? page.title : {};
    const htmlMap = typeof page.html === 'object' && page.html ? page.html : {};
    const translations = { ...(page.translations ?? {}) };

    for (const [locale, value] of Object.entries(titleMap)) {
      translations[locale] = { ...(translations[locale] ?? {}), title: value };
    }

    for (const [locale, value] of Object.entries(htmlMap)) {
      translations[locale] = { ...(translations[locale] ?? {}), html: value };
    }

    const slug = this.normalizeSlug(page.slug || page.id || key);
    return {
      id: page.id || key,
      slug,
      // route defaults from slug but is independently stored/editable once set.
      route: page.route ? this.normalizeSlug(page.route) : slug,
      title: typeof page.title === 'string' ? page.title : Object.values(titleMap)[0] ?? page.id ?? key,
      icon: page.icon,
      order: page.order ?? index + 1,
      showInFooter: page.showInFooter !== false,
      showInHeader: page.showInHeader === true,
      showInSitemap: page.showInSitemap !== false,
      visibility: page.visibility ?? { desktop: true, tablet: true, mobile: true },
      requiresAuthentication: page.requiresAuthentication === true,
      footerGroup: page.footerGroup,
      translations,
      seo: this.normalizeSeo(page.seo),
      enabled: page.enabled !== false,
      // Existing bootstrap data predates this field; treat it as already-published
      // so normalizing a previously-working page never silently un-publishes it.
      // Only the editor's "create page" action opts a brand-new page into 'draft'.
      status: page.status ?? 'published',
      customTemplate: page.customTemplate,
      heroImage: page.heroImage,
      heroImageAlt: page.heroImageAlt,
      heroImageCaption: page.heroImageCaption,
      thumbnail: page.thumbnail,
      gallery: page.gallery,
      updatedAt: page.updatedAt,
    };
  }

  private normalizeLegacyTranslations(title: LegacyTitle, content: LegacyContent): Record<string, { title?: string; html?: string }> {
    const translations: Record<string, { title?: string; html?: string }> = {};
    if (typeof title === 'string') {
      translations['en'] = { ...(translations['en'] ?? {}), title };
    } else {
      for (const [locale, value] of Object.entries(title ?? {})) {
        translations[locale] = { ...(translations[locale] ?? {}), title: value };
      }
    }

    if (typeof content === 'string') {
      translations['en'] = { ...(translations['en'] ?? {}), html: content };
    } else if (content && typeof content === 'object' && 'value' in content) {
      translations['en'] = { ...(translations['en'] ?? {}), html: content.value ?? '' };
    } else {
      for (const [locale, value] of Object.entries((content ?? {}) as LocalizedHtmlContent)) {
        translations[locale] = { ...(translations[locale] ?? {}), html: value };
      }
    }

    return translations;
  }

  normalizeSlug(value: string): string {
    return value.replace(/^\/+/, '').trim();
  }

  private normalizeSeo(input: ContentPageBootstrapInput['seo']): ContentPage['seo'] {
    if (!input) {
      return undefined;
    }

    const first = (value?: string | LocalizedTextContent) => typeof value === 'string' ? value : value?.['en'] ?? Object.values(value ?? {})[0];
    return {
      title: first(input.title),
      description: first(input.description),
      keywords: input.keywords,
      canonical: input.canonical,
      ogTitle: first(input.ogTitle),
      ogDescription: first(input.ogDescription),
      ogImage: input.ogImage,
      robots: (input as { robots?: string }).robots,
    };
  }
}

type LegacyTitle = string | LocalizedTextContent | undefined;
type LegacyContent = string | LocalizedHtmlContent | { value?: string; contentType?: string; source?: string } | undefined;
