/**
 * Alpine.js Schema Renderer Component
 *
 * Renders forms dynamically from JSON Schema with RJSF format support.
 * Supports uiSchema for widget hints, labels, help text, and field ordering.
 *
 * Usage:
 * <div x-data="schemaRenderer({
 *     rjsf: { schema: {...}, uiSchema: {...}, formData: {...}, groups: [...] },
 *     data: { interval: { start: 20, end: 25 } },
 *     mode: 'view', // 'view' | 'edit'
 *     endpoint: '/api/v1/scenario/{uuid}/information/temperature/'
 * })">
 */
document.addEventListener("alpine:init", () => {
  // Global validation error store with context scoping
  Alpine.store('validationErrors', {
    errors: {},

    // Set errors for a specific context (UUID) or globally (no context)
    setErrors(errors, context = null) {
      if (context) {
        // Namespace all field names with context prefix
        const namespacedErrors = {};
        Object.entries(errors).forEach(([field, messages]) => {
          const key = `${context}.${field}`;
          namespacedErrors[key] = messages;
        });
        // Merge into existing errors (preserves other contexts)
        this.errors = { ...this.errors, ...namespacedErrors };
      } else {
        // No context - merge as-is for backward compatibility
        this.errors = { ...this.errors, ...errors };
      }
    },

    // Clear errors for a specific context or all errors
    clearErrors(context = null) {
      if (context) {
        // Clear only errors for this context
        const newErrors = {};
        const prefix = `${context}.`;
        Object.keys(this.errors).forEach(key => {
          if (!key.startsWith(prefix)) {
            newErrors[key] = this.errors[key];
          }
        });
        this.errors = newErrors;
      } else {
        // Clear all errors
        this.errors = {};
      }
    },

    // Clear a specific field, optionally within a context
    clearField(fieldName, context = null) {
      const key = context ? `${context}.${fieldName}` : fieldName;
      if (this.errors[key]) {
        delete this.errors[key];
        // Trigger reactivity by creating new object
        this.errors = { ...this.errors };
      }
    },

    // Check if a field has errors, optionally within a context
    hasError(fieldName, context = null) {
      const key = context ? `${context}.${fieldName}` : fieldName;
      return Array.isArray(this.errors[key]) && this.errors[key].length > 0;
    },

    // Get errors for a field, optionally within a context
    getErrors(fieldName, context = null) {
      const key = context ? `${context}.${fieldName}` : fieldName;
      return this.errors[key] || [];
    }
  });

  Alpine.data("schemaRenderer", (options = {}) => ({
    schema: null,
    uiSchema: {},
    data: {},
    mode: options.mode || "view", // 'view' | 'edit'
    endpoint: options.endpoint || "",
    loading: false,
    error: null,
    context: options.context || null, // UUID for items, null for single forms
    debugErrors:
      options.debugErrors ??
      (typeof window !== "undefined" &&
        window.location?.search?.includes("debugErrors=1")),
    attach_object: options.attach_object || null,

    async init() {
      if (options.schemaUrl) {
        try {
          this.loading = true;
          const res = await fetch(options.schemaUrl);
          if (!res.ok) {
            throw new Error(`Failed to load schema: ${res.statusText}`);
          }
          const rjsf = await res.json();

          // RJSF format: {schema, uiSchema, formData, groups}
          if (!rjsf.schema) {
            throw new Error("Invalid RJSF format: missing schema property");
          }

          this.schema = rjsf.schema;
          this.uiSchema = rjsf.uiSchema || {};
          this.data = options.data
            ? JSON.parse(JSON.stringify(options.data))
            : rjsf.formData || {};
        } catch (err) {
          this.error = err.message;
          console.error("Error loading schema:", err);
        } finally {
          this.loading = false;
        }
      } else if (options.rjsf) {
        // Direct RJSF object passed
        if (!options.rjsf.schema) {
          throw new Error("Invalid RJSF format: missing schema property");
        }

        this.schema = options.rjsf.schema;
        this.uiSchema = options.rjsf.uiSchema || {};
        this.data = options.data
          ? JSON.parse(JSON.stringify(options.data))
          : options.rjsf.formData || {};
      }

      // Initialize data from formData or options
      if (!this.data || Object.keys(this.data).length === 0) {
        this.data = {};
      }

      // Ensure all schema fields are properly initialized
      if (this.schema && this.schema.properties) {
        for (const [key, propSchema] of Object.entries(
          this.schema.properties,
        )) {
          const widget = this.getWidget(key, propSchema);

          if (widget === "interval") {
            // Ensure interval fields are objects with start/end
            if (!this.data[key] || typeof this.data[key] !== "object") {
              this.data[key] = { start: null, end: null };
            } else {
              // Ensure start and end exist
              if (this.data[key].start === undefined)
                this.data[key].start = null;
              if (this.data[key].end === undefined) this.data[key].end = null;
            }
          } else if (widget === "timeseries-table") {
            // Ensure timeseries fields are arrays
            if (!this.data[key] || !Array.isArray(this.data[key])) {
              this.data[key] = [];
            }
          } else if (this.data[key] === undefined) {
            // ONLY initialize if truly undefined, not just falsy
            // This preserves empty strings, null, 0, false as valid values
            if (propSchema.type === "boolean") {
              this.data[key] = false;
            } else if (
              propSchema.type === "number" ||
              propSchema.type === "integer"
            ) {
              this.data[key] = null;
            } else if (propSchema.enum) {
              // For select fields, use null to show placeholder
              this.data[key] = null;
            } else {
              this.data[key] = "";
            }
          }
          // If data[key] exists (even if empty string or null), don't overwrite
        }
      }

      // UX: Clear field errors when fields change (with context)
      if (this.mode === "edit" && this.schema?.properties) {
        Object.keys(this.schema.properties).forEach((key) => {
          this.$watch(
            `data.${key}`,
            () => {
              Alpine.store('validationErrors').clearField(key, this.context);
            },
            { deep: true },
          );
        });
      }
    },

    getWidget(fieldName, fieldSchema) {
      // Defensive check: ensure fieldSchema is provided
      if (!fieldSchema) return "text";

      try {
        // Check uiSchema first (RJSF format)
        if (
          this.uiSchema &&
          this.uiSchema[fieldName] &&
          this.uiSchema[fieldName]["ui:widget"]
        ) {
          return this.uiSchema[fieldName]["ui:widget"];
        }

        // Check for interval type (object with start/end properties)
        if (
          fieldSchema.type === "object" &&
          fieldSchema.properties &&
          fieldSchema.properties.start &&
          fieldSchema.properties.end
        ) {
          return "interval";
        }

        // Check for measurements array type (timeseries-table widget)
        if (
          fieldSchema.type === "array" &&
          fieldSchema.items?.properties?.timestamp &&
          fieldSchema.items?.properties?.value
        ) {
          return "timeseries-table";
        }

        // Infer from JSON Schema type
        if (fieldSchema.enum) return "select";
        if (fieldSchema.type === "number" || fieldSchema.type === "integer")
          return "number";
        if (fieldSchema.type === "boolean") return "checkbox";
        return "text";
      } catch (e) {
        // Fallback to text widget if anything fails
        console.warn("Error in getWidget:", e);
        return "text";
      }
    },

    getLabel(fieldName, fieldSchema) {
      // Check uiSchema (RJSF format)
      if (this.uiSchema[fieldName] && this.uiSchema[fieldName]["ui:label"]) {
        return this.uiSchema[fieldName]["ui:label"];
      }

      // Default: format field name
      return fieldName
        .replace(/_/g, " ")
        .replace(/\b\w/g, (c) => c.toUpperCase());
    },

    getFieldOrder() {
      try {
        // Get ordered list of field names based on ui:order
        if (!this.schema || !this.schema.properties) return [];

        // Only include fields that have UI configs
        const fields = Object.keys(this.schema.properties).filter(
          (fieldName) => this.uiSchema && this.uiSchema[fieldName],
        );

        // Sort by ui:order if available
        return fields.sort((a, b) => {
          const orderA = this.uiSchema[a]?.["ui:order"] || "999";
          const orderB = this.uiSchema[b]?.["ui:order"] || "999";
          return parseInt(orderA) - parseInt(orderB);
        });
      } catch (e) {
        // Return empty array if anything fails to prevent errors
        console.warn("Error in getFieldOrder:", e);
        return [];
      }
    },

    hasTimeseriesField() {
      try {
        // Check if any field in the schema is a timeseries-table widget
        if (!this.schema || !this.schema.properties) {
          return false;
        }

        return Object.keys(this.schema.properties).some((fieldName) => {
          const fieldSchema = this.schema.properties[fieldName];
          if (!fieldSchema) return false;
          return this.getWidget(fieldName, fieldSchema) === "timeseries-table";
        });
      } catch (e) {
        // Return false if anything fails to prevent errors
        console.warn("Error in hasTimeseriesField:", e);
        return false;
      }
    },

    async submit() {
      if (!this.endpoint) {
        console.error("No endpoint specified for submission");
        return;
      }

      this.loading = true;
      this.error = null;

      try {
        const csrftoken =
          document.querySelector("[name=csrf-token]")?.content || "";
        const res = await fetch(this.endpoint, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-CSRFToken": csrftoken,
          },
          body: JSON.stringify(this.data),
        });

        if (!res.ok) {
          let errorData;
          try {
            errorData = await res.json();
          } catch {
            errorData = { error: res.statusText };
          }

          // Handle validation errors (field-level)
          Alpine.store('validationErrors').clearErrors();

          // Try to parse structured error response
          let parsedError = errorData;

          // If error is a JSON string, parse it
          if (
            typeof errorData.error === "string" &&
            errorData.error.startsWith("{")
          ) {
            parsedError = JSON.parse(errorData.error);
          }

          if (parsedError.detail && Array.isArray(parsedError.detail)) {
            // Pydantic validation errors format: [{loc: ['field'], msg: '...', type: '...'}]
            const fieldErrors = {};
            for (const err of parsedError.detail) {
              const field =
                err.loc && err.loc.length > 0
                  ? err.loc[err.loc.length - 1]
                  : "root";
              if (!fieldErrors[field]) {
                fieldErrors[field] = [];
              }
              fieldErrors[field].push(
                err.msg || err.message || "Validation error",
              );
            }
            Alpine.store('validationErrors').setErrors(fieldErrors);
            throw new Error(
              "Validation failed. Please check the fields below.",
            );
          } else {
            // General error
            throw new Error(
              parsedError.error ||
                parsedError.detail ||
                `Request failed: ${res.statusText}`,
            );
          }
        }

        // Clear errors on success
        Alpine.store('validationErrors').clearErrors();

        const result = await res.json();
        return result;
      } catch (err) {
        this.error = err.message;
        throw err;
      } finally {
        this.loading = false;
      }
    },
  }));
});
