import {
  DeesElement,
  html,
  customElement,
  type TemplateResult,
  css,
  state,
  property,
  cssManager,
} from '@design.estate/dees-element';
import * as interfaces from '../../../dist_ts_interfaces/index.js';

declare global {
  interface HTMLElementTagNameMap {
    'dns-provider-form': DnsProviderForm;
  }
}

/**
 * Reactive credential form for a DNS provider. Renders the type picker
 * and the credential fields for the currently-selected type.
 *
 * Provider-agnostic — driven entirely by `dnsProviderTypeDescriptors` from
 * `ts_interfaces/data/dns-provider.ts`. Adding a new provider type means
 * appending one entry to the descriptors array; this form picks it up
 * automatically.
 *
 * Usage:
 *
 *     const formEl = document.createElement('dns-provider-form');
 *     formEl.providerName = 'My provider';
 *     // ... pass element into a DeesModal as content ...
 *     // on submit:
 *     const data = formEl.collectData();
 *     // → { name, type, credentials }
 *
 * In edit mode, set `lockType = true` so the user cannot change provider
 * type after creation (credentials shapes don't transfer between types).
 */
@customElement('dns-provider-form')
export class DnsProviderForm extends DeesElement {
  /** Pre-populated provider name. */
  @property({ type: String })
  accessor providerName: string = '';

  /**
   * Currently selected provider type. Initialized to the first user-creatable
   * descriptor; caller can override before mounting (e.g. for edit dialogs).
   * The built-in 'dcrouter' pseudo-provider is excluded from the picker —
   * operators cannot create another one.
   */
  @state()
  accessor selectedType: interfaces.data.TDnsProviderType =
    interfaces.data.dnsProviderTypeDescriptors.find((d) => d.type !== 'dcrouter')?.type ??
    'cloudflare';

  /** When true, hide the type picker — used in edit dialogs. */
  @property({ type: Boolean })
  accessor lockType: boolean = false;

  /**
   * Help text shown above credentials. Useful for edit dialogs to indicate
   * that fields can be left blank to keep current values.
   */
  @property({ type: String })
  accessor credentialsHint: string = '';

  /** Internal map of credential field values, keyed by the descriptor's `key`. */
  @state()
  accessor credentialValues: Record<string, string> = {};

  public static styles = [
    cssManager.defaultStyles,
    css`
      :host {
        display: block;
      }

      .field {
        margin-bottom: 12px;
      }

      .credentialsHint {
        font-size: 12px;
        opacity: 0.7;
        margin-bottom: 12px;
      }
    `,
  ];

  public render(): TemplateResult {
    // Exclude the built-in 'dcrouter' pseudo-provider from the type picker —
    // operators cannot create another one, it's surfaced at read time by the
    // backend handler instead.
    const descriptors = interfaces.data.dnsProviderTypeDescriptors.filter(
      (d) => d.type !== 'dcrouter',
    );
    const descriptor = interfaces.data.getDnsProviderTypeDescriptor(this.selectedType);

    return html`
      <dees-form>
        <div class="field">
          <dees-input-text
            .key=${'name'}
            .label=${'Provider name'}
            .value=${this.providerName}
            .required=${true}
          ></dees-input-text>
        </div>

        ${this.lockType
          ? html`
              <div class="field">
                <dees-input-text
                  .key=${'__type_display'}
                  .label=${'Type'}
                  .infoText=${descriptor?.description || ''}
                  .value=${descriptor?.displayName ?? this.selectedType}
                  .disabled=${true}
                ></dees-input-text>
              </div>
            `
          : html`
              <div class="field">
                <dees-input-dropdown
                  .key=${'__type'}
                  .label=${'Provider type'}
                  .infoText=${descriptor?.description || ''}
                  .options=${descriptors.map((d) => ({ option: d.displayName, key: d.type }))}
                  .selectedOption=${descriptor
                    ? { option: descriptor.displayName, key: descriptor.type }
                    : undefined}
                  @selectedOption=${(e: CustomEvent) => {
                    const newType = (e.detail as any)?.key as
                      | interfaces.data.TDnsProviderType
                      | undefined;
                    if (newType && newType !== this.selectedType) {
                      this.selectedType = newType;
                      this.credentialValues = {};
                    }
                  }}
                ></dees-input-dropdown>
              </div>
            `}
        ${descriptor
          ? html`
              ${this.credentialsHint
                ? html`<div class="credentialsHint">${this.credentialsHint}</div>`
                : ''}
              ${descriptor.credentialFields.map(
                (f) => html`
                  <div class="field">
                    <dees-input-text
                      .key=${f.key}
                      .label=${f.label}
                      .description=${f.helpText || ''}
                      .required=${f.required && !this.lockType}
                    ></dees-input-text>
                  </div>
                `,
              )}
            `
          : html`<p>No provider types registered.</p>`}
      </dees-form>
    `;
  }

  /**
   * Read the form values and assemble the create/update payload.
   * Returns the typed credentials object built from the descriptor's keys.
   */
  public async collectData(): Promise<{
    name: string;
    type: interfaces.data.TDnsProviderType;
    credentials: interfaces.data.TDnsProviderCredentials;
    credentialsTouched: boolean;
  } | null> {
    const form = this.shadowRoot?.querySelector('dees-form') as any;
    if (!form) return null;
    const data = await form.collectFormData();
    const descriptor = interfaces.data.getDnsProviderTypeDescriptor(this.selectedType);
    if (!descriptor) return null;

    // Build the credentials object from the descriptor's field keys.
    const credsBody: Record<string, string> = {};
    let credentialsTouched = false;
    for (const f of descriptor.credentialFields) {
      const value = data[f.key];
      if (value !== undefined && value !== null && String(value).length > 0) {
        credsBody[f.key] = String(value);
        credentialsTouched = true;
      }
    }

    // The discriminator goes on the credentials object so the backend
    // factory and the discriminated union both stay happy.
    const credentials = {
      type: this.selectedType,
      ...credsBody,
    } as unknown as interfaces.data.TDnsProviderCredentials;

    return {
      name: String(data.name ?? ''),
      type: this.selectedType,
      credentials,
      credentialsTouched,
    };
  }
}
