import { EditorBlockSchema, TEditorConfiguration } from "@stackframe/stack-emails/dist/editor/documents/editor/core";
import { typedFromEntries } from "@stackframe/stack-shared/dist/utils/objects";
import { emailVerificationTemplate } from "./templates/email-verification";
import { passwordResetTemplate } from "./templates/password-reset";
import { magicLinkTemplate } from "./templates/magic-link";
import { render } from "@react-email/render";
import { Reader } from "@stackframe/stack-emails/dist/editor/email-builder/index";
import { Body, Head, Html, Preview } from "@react-email/components";
import * as Handlebars from 'handlebars/dist/handlebars.js';
import _ from 'lodash';
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { teamInvitationTemplate } from "./templates/team-invitation";


// TODO remove this one
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}


const userVars = [
  { name: 'userDisplayName', label: 'User Display Name', defined: false, example: 'John Doe' },
  { name: 'userPrimaryEmail', label: 'User Primary Email', defined: true, example: 'example@email.com' },
] as const;

const projectVars = [
  { name: 'projectDisplayName', label: 'Project Name', defined: true, example: '{{ projectDisplayName }}' },
];

export type EmailTemplateVariable = {
  name: string,
  label: string,
  defined: boolean,
  example: string,
};

export type EmailTemplateMetadata = {
  label: string,
  description: string,
  defaultContent: TEditorConfiguration,
  defaultSubject: string,
  variables: EmailTemplateVariable[],
};

export const EMAIL_TEMPLATES_METADATA = {
  'email_verification': {
    label: "Email Verification",
    description: "Will be sent to the user when they sign-up with email/password",
    defaultContent: emailVerificationTemplate,
    defaultSubject: "Verify your email at {{ projectDisplayName }}",
    variables: [
      ...userVars,
      ...projectVars,
      { name: 'emailVerificationLink', label: 'Email Verification Link', defined: true, example: '<email verification link>' },
    ],
  } satisfies EmailTemplateMetadata,
  'password_reset': {
    label: "Password Reset",
    description: "Will be sent to the user when they request to reset their password (forgot password)",
    defaultContent: passwordResetTemplate,
    defaultSubject: "Reset your password at {{ projectDisplayName }}",
    variables: [
      ...userVars,
      ...projectVars,
      { name: 'passwordResetLink', label: 'Reset Password Link', defined: true, example: '<reset password link>' },
    ],
  } satisfies EmailTemplateMetadata,
  'magic_link': {
    label: "Magic Link",
    description: "Will be sent to the user when they try to sign-up with magic link",
    defaultContent: magicLinkTemplate,
    defaultSubject: "Sign in to {{ projectDisplayName }}",
    variables: [
      ...userVars,
      ...projectVars,
      { name: 'magicLink', label: 'Magic Link', defined: true, example: '<magic link>' },
    ],
  } satisfies EmailTemplateMetadata,
  'team_invitation': {
    label: "Team Invitation",
    description: "Will be sent to the user when they are invited to a team",
    defaultContent: teamInvitationTemplate,
    defaultSubject: "You have been invited to join {{ teamDisplayName }}",
    variables: [
      ...userVars,
      ...projectVars,
      { name: 'teamDisplayName', label: 'Team Display Name', defined: true, example: 'My Team' },
      { name: 'teamInvitationLink', label: 'Team Invitation Link', defined: true, example: '<team invitation link>' },
    ],
  } satisfies EmailTemplateMetadata,
} as const;

export function validateEmailTemplateContent(content: any): content is TEditorConfiguration {
  try {
    for (const key of Object.keys(content)) {
      const block = content[key];
      EditorBlockSchema.parse(block);
    }
    return true;
  } catch (e) {
    console.error(e);
    return false;
  }
}

type NestedObject = { [key: string]: any };

export function objectStringMap<T extends NestedObject>(obj: T, func: (s: string) => string): T {
  function mapStrings(obj: NestedObject): NestedObject {
    const result: NestedObject = Array.isArray(obj) ? [] : {};

    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const value = obj[key];

        if (typeof value === 'string') {
          result[key] = func(value);
        } else if (typeof value === 'object' && value !== null) {
          result[key] = mapStrings(value);
        } else {
          result[key] = value;
        }
      }
    }

    return result;
  }

  return mapStrings(obj) as T;
}

function renderString(str: string, variables: Record<string, string | null>) {
  try {
    return Handlebars.compile(str, {noEscape: true})(variables);
  } catch (e) {
    return str;
  }
}

export function convertEmailTemplateMetadataExampleValues(
  metadata: EmailTemplateMetadata,
  projectDisplayName: string,
): EmailTemplateMetadata {
  const variables = metadata.variables.map((variable) => {
    return {
      ...variable,
      example: renderString(variable.example, { projectDisplayName }),
    };
  });
  return {
    ...metadata,
    variables,
  };
}

export function convertEmailTemplateVariables(
  content: TEditorConfiguration,
  variables: EmailTemplateVariable[],
): TEditorConfiguration {
  const vars = typedFromEntries(variables.map((variable) => [variable.name, variable.example]));
  return objectStringMap(content, (str) => {
    return renderString(str, vars);
  });
}

export function convertEmailSubjectVariables(
  subject: string,
  variables: EmailTemplateVariable[],
): string {
  const vars = typedFromEntries(variables.map((variable) => [variable.name, variable.example]));
  return renderString(subject, vars);
}

export function renderEmailTemplate(
  subject: string,
  content: TEditorConfiguration,
  variables: Record<string, string | null>,
) {
  const mergedTemplate = objectStringMap(content, (str) => {
    return renderString(str, variables);
  });
  const mergedSubject = renderString(subject, variables);

  const component = (
    <Html>
      <Head />
      <Preview>{mergedSubject}</Preview>
      <Body>
        <Reader document={mergedTemplate} rootBlockId='root' />
      </Body>
    </Html>
  );
  const html = render(component);
  const text = render(component, { plainText: true });

  return { html, text, subject: mergedSubject };
}
