import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type {
  AuthenticationType,
  HttpMethod,
  RestAction,
} from '../../types/project';
import {
  HTTP_METHODS,
  isAbsoluteHttpUrl,
  keyValueRowsToRecord,
  validateKeyValueRows,
  validateRestAction,
} from './actionEditorUtils';
import type {
  KeyValueRow,
  RequestMapKeyKind,
} from './actionEditorUtils';
import styles from './ActionInspector.module.css';

const AUTHENTICATION_OPTIONS: Array<{
  value: AuthenticationType;
  label: string;
}> = [
  { value: 'anonymous', label: 'Anonymous' },
  { value: 'bearerToken', label: 'Bearer token (Slice 3)' },
  { value: 'basicAuth', label: 'Basic authentication (Slice 3)' },
  { value: 'apiKeyHeader', label: 'API key header (Slice 3)' },
  { value: 'apiKeyQueryParameter', label: 'API key query parameter (Slice 3)' },
];

type KeyValueEditorProps = {
  fieldName: string;
  title: string;
  keyPlaceholder: string;
  valuePlaceholder?: string;
  keyKind?: RequestMapKeyKind;
  value: Record<string, string>;
  onChange: (value: Record<string, string>) => void;
  onValidityChange: (valid: boolean) => void;
};

function makeRows(value: Record<string, string>): KeyValueRow[] {
  return Object.entries(value).map(([key, entryValue], index) => ({
    id: index + 1,
    key,
    value: entryValue,
  }));
}

function KeyValueEditor({
  fieldName,
  title,
  keyPlaceholder,
  valuePlaceholder = 'Value or {{components.name.value}}',
  keyKind = 'generic',
  value,
  onChange,
  onValidityChange,
}: KeyValueEditorProps): React.ReactElement {
  const [rows, setRows] = useState<KeyValueRow[]>(() => makeRows(value));
  const [error, setError] = useState<string | null>(null);
  const nextRowId = useRef(rows.length + 1);
  const lastCanonicalValue = useRef(value);

  useEffect(() => {
    if (value === lastCanonicalValue.current) return;

    const nextRows = makeRows(value);
    lastCanonicalValue.current = value;
    nextRowId.current = nextRows.length + 1;
    setRows(nextRows);
    setError(null);
    onValidityChange(true);
  }, [onValidityChange, value]);

  const commitRows = useCallback(
    (nextRows: KeyValueRow[]) => {
      setRows(nextRows);
      const validationError = validateKeyValueRows(nextRows, keyKind);
      setError(validationError);
      onValidityChange(validationError === null);
      if (validationError) return;

      const nextRecord = keyValueRowsToRecord(nextRows);
      lastCanonicalValue.current = nextRecord;
      onChange(nextRecord);
    },
    [keyKind, onChange, onValidityChange],
  );

  const handleAdd = useCallback(() => {
    setRows((current) => [
      ...current,
      { id: nextRowId.current++, key: '', value: '' },
    ]);
    setError('Every row needs a key before it can be added to the project document.');
    onValidityChange(false);
  }, [onValidityChange]);

  const handleChange = useCallback(
    (rowId: number, field: 'key' | 'value', nextValue: string) => {
      commitRows(
        rows.map((row) =>
          row.id === rowId ? { ...row, [field]: nextValue } : row,
        ),
      );
    },
    [commitRows, rows],
  );

  const handleRemove = useCallback(
    (rowId: number) => {
      commitRows(rows.filter((row) => row.id !== rowId));
    },
    [commitRows, rows],
  );

  return (
    <div
      className={styles.keyValueEditor}
      data-testid={`action-${fieldName}`}
    >
      <div className={styles.keyValueHeader}>
        <span className={styles.formLabel}>{title}</span>
        <button
          type="button"
          className={styles.smallButton}
          onClick={handleAdd}
          aria-label={`Add ${title.toLowerCase()} row`}
        >
          + Add row
        </button>
      </div>

      {rows.length === 0 && (
        <div className={styles.formHint}>No entries configured.</div>
      )}

      {rows.map((row) => (
        <div className={styles.keyValueRow} key={row.id}>
          <input
            className={styles.formInput}
            aria-label={`${title} key`}
            placeholder={keyPlaceholder}
            value={row.key}
            onChange={(event) => handleChange(row.id, 'key', event.target.value)}
          />
          <input
            className={styles.formInput}
            aria-label={`${title} value`}
            placeholder={valuePlaceholder}
            value={row.value}
            onChange={(event) => handleChange(row.id, 'value', event.target.value)}
          />
          <button
            type="button"
            className={styles.removeRowButton}
            onClick={() => handleRemove(row.id)}
            aria-label={`Remove ${title.toLowerCase()} row`}
          >
            Remove
          </button>
        </div>
      ))}

      {error && (
        <div className={styles.inlineError} role="alert">
          {error} Invalid rows remain local until corrected.
        </div>
      )}
    </div>
  );
}

export type RestActionEditorProps = {
  action: RestAction;
  actions: RestAction[];
  onChange: (action: RestAction) => void;
  onDone: () => void;
};

function RestActionEditor({
  action,
  actions,
  onChange,
  onDone,
}: RestActionEditorProps): React.ReactElement {
  const [nameDraft, setNameDraft] = useState(action.name);
  const [urlDraft, setUrlDraft] = useState(action.url);
  const [invalidMapFields, setInvalidMapFields] = useState<Set<string>>(() => new Set());
  const lastEmittedAction = useRef<RestAction | null>(null);

  const nameDraftValid = nameDraft.trim().length > 0;
  const urlDraftValid = isAbsoluteHttpUrl(urlDraft);
  const hasInvalidDrafts =
    !nameDraftValid || !urlDraftValid || invalidMapFields.size > 0;

  const issues = useMemo(
    () => validateRestAction(action, actions),
    [action, actions],
  );

  useEffect(() => {
    if (action === lastEmittedAction.current) {
      lastEmittedAction.current = null;
      return;
    }

    setNameDraft(action.name);
    setUrlDraft(action.url);
    setInvalidMapFields(new Set());
  }, [action]);

  const update = useCallback(
    <K extends keyof RestAction>(field: K, value: RestAction[K]) => {
      const nextAction = { ...action, [field]: value };
      lastEmittedAction.current = nextAction;
      onChange(nextAction);
    },
    [action, onChange],
  );
  const handleNameDraftChange = useCallback(
    (value: string) => {
      setNameDraft(value);
      if (value.trim()) update('name', value);
    },
    [update],
  );

  const handleUrlDraftChange = useCallback(
    (value: string) => {
      setUrlDraft(value);
      if (isAbsoluteHttpUrl(value)) update('url', value);
    },
    [update],
  );

  const handleMapValidityChange = useCallback(
    (fieldName: string, valid: boolean) => {
      setInvalidMapFields((current) => {
        const alreadyInvalid = current.has(fieldName);
        if ((valid && !alreadyInvalid) || (!valid && alreadyInvalid)) {
          return current;
        }

        const next = new Set(current);
        if (valid) next.delete(fieldName);
        else next.add(fieldName);
        return next;
      });
    },
    [],
  );


  const editorId = `rest-action-editor-${action.id}`;

  return (
    <form
      className={styles.actionEditor}
      aria-label={`Edit REST action ${action.name}`}
      data-testid={editorId}
      onSubmit={(event) => {
        event.preventDefault();
        if (hasInvalidDrafts) return;
        onDone();
      }}
    >
      <div className={styles.actionEditorHeader}>
        <div>
          <div className={styles.actionEditorTitle}>Edit REST action</div>
          <div className={styles.formHint}>
            Valid edits update the canonical project document immediately.
          </div>
        </div>
        <button
          type="submit"
          className={styles.primaryButton}
          disabled={hasInvalidDrafts}
          title={hasInvalidDrafts ? 'Resolve invalid local drafts before closing.' : undefined}
        >
          Done
        </button>
      </div>

      {issues.length > 0 && (
        <div className={styles.editorIssues} aria-label="Action validation">
          {issues.map((issue, index) => (
            <div
              key={`${issue.field}-${index}`}
              className={
                issue.severity === 'warn'
                  ? styles.editorIssueWarn
                  : styles.editorIssueInfo
              }
            >
              {issue.message}
            </div>
          ))}
        </div>
      )}

      <div className={styles.formGrid}>
        <label className={styles.formField}>
          <span className={styles.formLabel}>Action ID</span>
          <input
            className={styles.formInput}
            data-testid="action-id"
            value={action.id}
            readOnly
          />
          <span className={styles.formHint}>
            Stable because component events and bindings reference this ID.
          </span>
        </label>

        <label className={styles.formField}>
          <span className={styles.formLabel}>Name</span>
          <input
            className={styles.formInput}
            data-testid="action-name"
            value={nameDraft}
            onChange={(event) => handleNameDraftChange(event.target.value)}
          />
          {!nameDraftValid && (
            <span className={styles.inlineError} role="alert">
              Name is required. The last valid canonical value is preserved.
            </span>
          )}
        </label>
      </div>

      <label className={styles.formField}>
        <span className={styles.formLabel}>Description</span>
        <textarea
          className={styles.formTextarea}
          data-testid="action-description"
          value={action.description ?? ''}
          onChange={(event) => update('description', event.target.value)}
          rows={2}
        />
      </label>

      <div className={styles.endpointRow}>
        <label className={styles.methodField}>
          <span className={styles.formLabel}>HTTP method</span>
          <select
            className={styles.formSelect}
            data-testid="action-method"
            value={action.method}
            onChange={(event) =>
              update('method', event.target.value as HttpMethod)
            }
          >
            {HTTP_METHODS.map((method) => (
              <option key={method} value={method}>{method}</option>
            ))}
          </select>
        </label>

        <label className={styles.urlField}>
          <span className={styles.formLabel}>Endpoint URL</span>
          <input
            className={styles.formInput}
            data-testid="action-url"
            value={urlDraft}
            placeholder="https://api.example.com/items/{{itemId}}"
            onChange={(event) => handleUrlDraftChange(event.target.value)}
          />
          {!urlDraftValid && (
            <span className={styles.inlineError} role="alert">
              Enter absolute HTTP or HTTPS without spaces or embedded credentials.
            </span>
          )}
        </label>
      </div>

      <label className={styles.formField}>
        <span className={styles.formLabel}>Authentication</span>
        <select
          className={styles.formSelect}
          data-testid="action-authentication"
          value={action.authenticationType}
          onChange={(event) => {
            const nextAuthenticationType =
              event.target.value as AuthenticationType;
            if (nextAuthenticationType === 'anonymous') {
              update('authenticationType', nextAuthenticationType);
            } else {
              event.currentTarget.value = action.authenticationType;
            }
          }}
        >
          {AUTHENTICATION_OPTIONS.map((option) => (
            <option
              key={option.value}
              value={option.value}
              disabled={option.value !== 'anonymous'}
            >
              {option.label}
            </option>
          ))}
        </select>
        <span className={styles.formHint}>
          Slice 2 creates anonymous actions only. Authentication and secrets are
          configured in Slice 3.
        </span>
      </label>

      <details className={styles.actionEditorDetails} open>
        <summary>Request parameters</summary>
        <div className={styles.detailsBody}>
          <KeyValueEditor
            key={`${action.id}-headers`}
            fieldName="headers"
            title="Headers"
            keyPlaceholder="Accept"
            keyKind="header"
            value={action.headers ?? {}}
            onChange={(nextValue) => update('headers', nextValue)}
            onValidityChange={(valid) =>
              handleMapValidityChange('headers', valid)
            }
          />
          <KeyValueEditor
            key={`${action.id}-query`}
            fieldName="query-parameters"
            title="Query parameters"
            keyPlaceholder="limit"
            value={action.queryParameters ?? {}}
            onChange={(nextValue) => update('queryParameters', nextValue)}
            onValidityChange={(valid) =>
              handleMapValidityChange('queryParameters', valid)
            }
          />
          <KeyValueEditor
            key={`${action.id}-path`}
            fieldName="path-parameters"
            title="Path parameters"
            keyPlaceholder="itemId"
            keyKind="path"
            valuePlaceholder="Static replacement value (templates are not supported)"
            value={action.pathParameters ?? {}}
            onChange={(nextValue) => update('pathParameters', nextValue)}
            onValidityChange={(valid) =>
              handleMapValidityChange('pathParameters', valid)
            }
          />
        </div>
      </details>

      <details className={styles.actionEditorDetails} open>
        <summary>Request body</summary>
        <div className={styles.detailsBody}>
          <label className={styles.formField}>
            <span className={styles.formLabel}>Body template</span>
            <textarea
              className={[styles.formTextarea, styles.bodyTemplate].join(' ')}
              data-testid="action-body"
              value={action.bodyTemplate ?? ''}
              placeholder={'{"name":"{{components.nameInput.value}}"}'}
              onChange={(event) => update('bodyTemplate', event.target.value)}
              rows={7}
              spellCheck={false}
            />
            {(action.method === 'GET' || action.method === 'DELETE') && (
              <span className={styles.formHint}>
                {action.method} requests retain this configuration but do not
                send a request body.
              </span>
            )}
          </label>
        </div>
      </details>
    </form>
  );
}

export default RestActionEditor;
