/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { loadModule} from "./utils.ts";
import { getExternalContentMock } from "./mock";

let distFolderPath: string | null = null;
let currentApp: any = null;

export function setDistFolderPath(path: string): void {
  distFolderPath = path;
}

interface HSLColor {
  h: number;
  s: number;
  l: number;
}

interface RGBAColor {
  r: number;
  g: number;
  b: number;
  a: number;
}

interface ColorObject {
  hex: string;
  hsl: HSLColor;
  rgba: RGBAColor;
}

function hexToColorObject(hex: string): ColorObject {
  // Support both 6-digit (#RRGGBB) and 8-digit (#RRGGBBAA) hex colors
  const match = /^#?([0-9a-fA-F]{6}([0-9a-fA-F]{2})?)$/.exec(hex);
  if (!match) throw new Error("Invalid hex color format");
  const cleanHex = match[1].toLowerCase();
  const r = parseInt(cleanHex.substring(0, 2), 16);
  const g = parseInt(cleanHex.substring(2, 4), 16);
  const b = parseInt(cleanHex.substring(4, 6), 16);
  // Extract alpha if present (8-digit hex), otherwise default to 255 (fully opaque)
  const a = cleanHex.length === 8 ? parseInt(cleanHex.substring(6, 8), 16) : 255;
  const rNorm = r / 255, gNorm = g / 255, bNorm = b / 255;
  const max = Math.max(rNorm, gNorm, bNorm), min = Math.min(rNorm, gNorm, bNorm);
  const delta = max - min;
  let h = 0, s = 0, l = (max + min) / 2;
  if (delta !== 0) {
    s = delta / (1 - Math.abs(2 * l - 1));
    switch (max) {
      case rNorm: h = ((gNorm - bNorm) / delta) % 6; break;
      case gNorm: h = (bNorm - rNorm) / delta + 2; break;
      case bNorm: h = (rNorm - gNorm) / delta + 4; break;
    }
    h *= 60;
    if (h < 0) h += 360;
  }
  return {
    hex: `#${cleanHex.substring(0, 6)}${a.toString(16).padStart(2, '0')}`,
    hsl: { h: Math.round(h), s: +(s * 100).toFixed(1), l: +(l * 100).toFixed(1) },
    rgba: { r, g, b, a: +(a / 255).toFixed(2) },
  };
}

function updateHexColors(obj: any): any {
  // Matches 3-digit (#RGB), 6-digit (#RRGGBB), or 8-digit (#RRGGBBAA) hex
  const hexRegex = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;

  function recurse(value: any): any {
    if (Array.isArray(value)) return value.map(recurse);
    if (value && typeof value === 'object') {
      for (const key in value) {
        const v = value[key];
        if (key === 'color' && typeof v === 'string' && hexRegex.test(v)) {
          // If it’s a 3-digit hex, expand it to 6 digits
          const expanded = v.replace(
            /^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/,
            (_m, r, g, b) => `#${r}${r}${g}${g}${b}${b}`
          );
          value[key] = hexToColorObject(expanded);
        } else {
          value[key] = recurse(v);
        }
      }
    }
    return value;
  }
  return recurse(obj);
}

const replaceGlobalFont = (obj: any, font: string): void => {
  Object.entries(obj).forEach(([k, v]) => {
    if (v && typeof v === 'object') {
      replaceGlobalFont(v, font);
    } else if (k === 'font' && v === 'global.fontFamily.body') {
      obj[k] = font;
    }
  });
};

function createBackgroundStructure(type: 'solid' | 'gradient', config: any): any {
  return {
    background: {
      background: {
        type,
        ...config,
      },
    },
  };
}

// Helper function to create default gray background
function createDefaultBackground(): any {
  const defaultGrayColor = hexToColorObject('#F9F9F9');
  return createBackgroundStructure('solid', {
    solid: { color: defaultGrayColor },
    color: 'global.color.background',
  });
}

// Helper function to check if color is global reference
function isGlobalColor(color: string): boolean {
  return typeof color === 'string' && color.startsWith('global.');
}

function createBackgroundDesign(showcaseBackground: any): any {
  // No background or invalid background
  if (!showcaseBackground) {
    return createDefaultBackground();
  }

  // Handle gradient backgrounds
  if (showcaseBackground.style === 'GRADIENT' && Array.isArray(showcaseBackground.color)) {
    const [fromColor, toColor] = showcaseBackground.color;

    // If either color is global, use default
    if (isGlobalColor(fromColor) || isGlobalColor(toColor)) {
      return createDefaultBackground();
    }

    // Create gradient background
    return createBackgroundStructure('gradient', {
      solid: { color: hexToColorObject(fromColor) },
      gradient: {
        fromColor: hexToColorObject(fromColor),
        toColor: hexToColorObject(toColor),
      },
      color: `gradient(${fromColor}, ${toColor})`,
    });
  }

  // Handle solid color backgrounds
  const solidColor = showcaseBackground.color;

  // Global color reference
  if (isGlobalColor(solidColor)) {
    return createDefaultBackground();
  }

  // Specific hex color
  if (typeof solidColor === 'string') {
    return createBackgroundStructure('solid', {
      solid: { color: hexToColorObject(solidColor) },
      color: solidColor,
    });
  }

  // Fallback to default
  return createDefaultBackground();
}

function overrideSettingsFromShowcase(content: any, showcases: any): any {
  return Object.fromEntries(Object.entries(content).map(([k, v]) => [k, showcases[k] ?? v]));
}

export function mergeDesign(
  design: Record<string, any>,
  showcase: Record<string, any>
): Record<string, any> {
  const result: Record<string, any> = { ...design };

  Object.keys(design).forEach((key) => {
    if (key in showcase) {
      const base = design[key];
      const override = showcase[key];

      if (
        base !== null && override !== null &&
        typeof base === 'object' && typeof override === 'object' &&
        !Array.isArray(base) && !Array.isArray(override)
      ) {
        // shallow merge nested objects
        result[key] = { ...base, ...override };
      } else {
        // replace with showcase value (covers primitives & arrays)
        result[key] = override;
      }
    }
  });

  return result;
}

export function designTransformer(design: Record<string, any>, showCaseDesign: Record<string, any> ): Record<string, any> {
  const parsedDesign: Record<string, any> = {};
  const parsedShowcaseDesign: Record<string, any> = {};
  Object.entries(design).forEach(([key, comp]) => {
    parsedDesign[key] = comp?.defaults;
  });

  Object.entries(showCaseDesign).forEach(([key, comp]) => {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const { type, ...withoutType } = comp;
    parsedShowcaseDesign[key] = type == 'TEXT' ? { ... withoutType, visible: true} : withoutType
  });

  let overridenDesign = mergeDesign(parsedDesign, parsedShowcaseDesign)
  overridenDesign = updateHexColors(overridenDesign);
  replaceGlobalFont(overridenDesign, 'Roboto');
  return overridenDesign;
}

function processImage(component: any, sectionName: string, key: string): Record<string, any> {
  const assetLocation = `${distFolderPath}/sections/${sectionName}/assets/`;
  const set = component.defaults?.set || component.imageData?.set;
  const newSet = {
    'cropped-webp-100x200': { url: assetLocation + set.MOBILE_WEBP_LOW_RES.url },
    'cropped-webp-1000x2000': { url: assetLocation + set.MOBILE_WEBP_HI_RES.url },
    'webp-200x200': { url: assetLocation + set.WEBP_LOW_RES.url },
    'webp-2000x2000': { url: assetLocation + set.WEBP_HI_2X_RES.url },
  };
  return {
    [key]: {
      set: newSet,
      ...(component.imageData?.borderInfo && { borderInfo: component.imageData.borderInfo }),
      ...(component.imageData && { bucket: {} })
    },
  };
}

function processComponent(key: string, component: any, translations: any, sectionName?: string): any {
  if (!component?.type) return '';
  switch (component.type) {
    case 'INPUTBOX':
    case 'TEXTAREA': {
      const text = component?.defaults?.text ?? component?.text ?? component?.placeholder;
      return { [key]: translations[text] };
    }
    case 'DIVIDER':
      return { [key]: translations[component.label] };
    case 'DECK': {
      const defaultSettings = component.cards?.defaultCardContent?.settings;
      if (defaultSettings) {
        const result: Record<string, any> = {};
        Object.entries(defaultSettings).forEach(([k, c]) => {
          Object.assign(result, processComponent(k, c, translations));
        });
        return { [key]: result };
      } else {
        const cards = component.cards.map((card: any) => {
          const cardContent: Record<string, any> = {};
          Object.entries(card.settings).forEach(([k, c]) => {
            Object.assign(cardContent, processComponent(k, c, translations, sectionName));
          });
          return { settings: cardContent };
        });
        return { [key]: { cards } };
      }
    }
    case 'BUTTON': {
      const button = component.defaults || component;
      return {
        [key]: {
          title: translations[button.title],
          type: button.buttonType,
          link: button.link,
        },
      };
    }
    case 'TOGGLE':
      return { [key]: component.defaults };
    case 'SELECTBOX':
      return { [key]: component.defaults.value };
    case 'IMAGE':
      return processImage(component, sectionName!, key);
    default:
      console.warn(`Unknown type: ${component.type}`);
      return '';
  }
}

function getContentToRender(
  content: any,
  showcase: any,
  contentTranslations: any,
  showcaseTranslations: any,
  sectionName: string
): any {
  const parsedContent = Object.entries(content).reduce((acc, [k, c]) => {
    return { ...acc, ...processComponent(k, c, contentTranslations, sectionName) };
  }, {});

  const parsedShowcase = Object.entries(showcase).reduce((acc, [k, c]) => {
    return { ...acc, ...processComponent(k, c, showcaseTranslations, sectionName) };
  }, {});

  return overrideSettingsFromShowcase(parsedContent, parsedShowcase);
}

export function dropdownOptions(showcaseModules: Record<string, any>): Array<{ value: string; label: string }> {
  return Object.keys(showcaseModules).map(path => {
    const match = path.match(/\/sections\/([^/]+)\/js\/showcases\/(\d+)\.mjs$/);
    if (!match) return null;
    return {
      value: `${match[1]}:${match[2]}`,
      label: `${match[1]}: showcase ${match[2]}`,
    };
  }).filter(Boolean) as Array<{ value: string; label: string }>;
}

export function loadSectionCss(sectionName: string): void {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = `${distFolderPath}/sections/${sectionName}/js/main/client/assets/client.css`;
  document.head.appendChild(link);
}

export async function renderShowcase(sectionName: string, showcaseId: string): Promise<void> {
  const client = await loadModule(`${distFolderPath}/sections/${sectionName}/js/main/client/client.js`);
  const content = await loadModule(`${distFolderPath}/sections/${sectionName}/js/settings/content.mjs`);
  const contentTranslations = await loadModule(`${distFolderPath}/sections/${sectionName}/js/settings/translations.mjs`);
  const showcaseTranslations = await loadModule(`${distFolderPath}/sections/${sectionName}/js/showcases/translations.mjs`);
  const showcase = await loadModule(`${distFolderPath}/sections/${sectionName}/js/showcases/${showcaseId}.mjs`);
  const design = await loadModule(`${distFolderPath}/sections/${sectionName}/js/settings/design.mjs`);

  // Get showcase background and create background design
  const showcaseBackground = showcase.default?.design?.background;
  const backgroundDesign = createBackgroundDesign(showcaseBackground);

  const ovveridenDesign = designTransformer(design.default, showcase.default.design || {});

  loadSectionCss(sectionName);

  const overriddenContent = getContentToRender(
    content.default,
    showcase.default.content || {},
    contentTranslations.default.en,
    showcaseTranslations.default.en,
    sectionName
  );

  // Get external content mock (can be customized in compiled mock.js)
  const externalContentMock = getExternalContentMock();

  // Ensure background design always overrides any existing background
  const finalDesign = { ...ovveridenDesign };
  finalDesign.background = backgroundDesign.background;

  const state = {
    context: {
      globalDesign: { color: 'global.color.background' },
    },
    data: {
      content: overriddenContent,
      design: finalDesign,
      defaults: {},
      background: {},
      externalContent: externalContentMock,
    },
  };

  // If app is already mounted, update it; otherwise mount it
  if (currentApp) {
    currentApp.update(state);
  } else {
    const { mount, update, unmount } = client.default.init();
    currentApp = { update, unmount };
    mount('#app', state);
  }
}
