<script setup lang="ts">
import { computed, defineAsyncComponent, ref } from 'vue';
import { useRequestStore, type RequestError, type KeyValue } from '../stores/requestStore';
import KeyValueEditor from './KeyValueEditor.vue';
import AuthPanel from './AuthPanel.vue';
import CustomSelect from './CustomSelect.vue';
import { invoke } from '@tauri-apps/api/core';
import { Play, Loader2, Square } from 'lucide-vue-next';

const CodeEditor = defineAsyncComponent(() => import('./CodeEditor.vue'));
const store = useRequestStore();
const activeTab = ref('params');
const isLoading = ref(false);
const currentRequestId = ref<string | null>(null);
const bodyMode = ref<'auto' | 'json' | 'text'>('auto');

const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
const bodyModeOptions = [
  { value: 'auto', label: 'Auto' },
  { value: 'json', label: 'JSON' },
  { value: 'text', label: 'Text' },
];

const methodSupportsBody = computed(() => !['GET', 'HEAD'].includes(store.activeRequest.method));

const inferredBodyLanguage = computed<'json' | 'text'>(() => {
  if (bodyMode.value !== 'auto') return bodyMode.value;

  const contentTypeHeader = store.activeRequest.headers.find((header) => (
    header.enabled && header.key.trim().toLowerCase() === 'content-type'
  ));
  const contentType = contentTypeHeader?.value.trim().toLowerCase() || '';
  return contentType.includes('json') ? 'json' : 'text';
});

const requestBodyPlaceholder = computed(() => (
  inferredBodyLanguage.value === 'json'
    ? 'Request Body (JSON)'
    : 'Request Body'
));

const normalizeError = (error: unknown): RequestError => {
  if (error && typeof error === 'object' && 'code' in error && 'message' in error) {
    return {
      code: String((error as { code: unknown }).code),
      message: String((error as { message: unknown }).message),
    };
  }

  return {
    code: 'unknown',
    message: String(error),
  };
};

const findDuplicateKey = (items: KeyValue[]) => {
  const seen = new Set<string>();

  for (const item of items) {
    const key = item.key.trim().toLowerCase();
    if (!item.enabled || !key) continue;
    if (seen.has(key)) return item.key.trim();
    seen.add(key);
  }

  return null;
};

const validateRequest = (): RequestError | null => {
  const url = store.activeRequest.url.trim();
  if (!url) {
    return { code: 'validation', message: 'URL is required.' };
  }

  let parsedUrl: URL;
  try {
    parsedUrl = new URL(url);
  } catch {
    return { code: 'invalid_url', message: 'URL is invalid. Use a full http:// or https:// address.' };
  }

  if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
    return { code: 'invalid_url', message: 'Only http:// and https:// URLs are supported.' };
  }

  const duplicateHeader = findDuplicateKey(store.activeRequest.headers);
  if (duplicateHeader) {
    return { code: 'duplicate_header', message: `Duplicate enabled header: ${duplicateHeader}` };
  }

  const duplicateParam = findDuplicateKey(store.activeRequest.params);
  if (duplicateParam) {
    return { code: 'duplicate_query_param', message: `Duplicate enabled query param: ${duplicateParam}` };
  }

  if (inferredBodyLanguage.value === 'json' && store.activeRequest.body.trim()) {
    try {
      JSON.parse(store.activeRequest.body);
    } catch {
      return { code: 'invalid_json', message: 'Request body is set to JSON mode but contains invalid JSON.' };
    }
  }

  return null;
};

const executeRequest = async () => {
  if (isLoading.value || !store.activeRequest.url) return;

  const validationError = validateRequest();
  if (validationError) {
    store.setRequestError(validationError);
    return;
  }

  const requestId = crypto.randomUUID();
  currentRequestId.value = requestId;
  isLoading.value = true;
  store.setRequestError(null);
  
  try {
    const headers: Record<string, string> = {};
    store.activeRequest.headers.filter(h => h.enabled && h.key).forEach(h => headers[h.key] = h.value);
    
    const params: Record<string, string> = {};
    store.activeRequest.params.filter(p => p.enabled && p.key).forEach(p => params[p.key] = p.value);

    const response: any = await invoke('execute_request', {
      requestId,
      method: store.activeRequest.method,
      url: store.activeRequest.url,
      headers,
      body: store.activeRequest.body.trim() ? store.activeRequest.body : null,
      queryParams: params,
      auth: store.activeRequest.auth,
    });

    store.setRequestError(null);
    store.activeRequest.response = {
        status: response.status,
        headers: response.headers,
        body: response.body,
        time: response.time_elapsed,
        size: response.total_size,
        headersSize: response.headers_size,
        bodySize: response.body_size,
    };

    store.addToHistory(store.activeRequest);

  } catch (error) {
    console.error(error);
    store.activeRequest.response = undefined;
    store.setRequestError(normalizeError(error));
  } finally {
    currentRequestId.value = null;
    isLoading.value = false;
  }
};

const cancelRequest = async () => {
  if (!currentRequestId.value) return;

  try {
    await invoke('cancel_request', { requestId: currentRequestId.value });
  } catch (error) {
    console.error(error);
    store.setRequestError(normalizeError(error));
  }
};
</script>

<template>
  <div class="flex flex-col h-full bg-slate-900">
    <!-- Top Bar -->
    <div class="p-4 border-b border-slate-800 flex gap-2 items-center">
      <div class="flex-1 flex items-center bg-slate-950 rounded-lg border border-slate-800 focus-within:border-indigo-500/50 focus-within:ring-1 focus-within:ring-indigo-500/50 transition-all overflow-visible z-20">
        <CustomSelect
            v-model="store.activeRequest.method"
            :options="methods"
            triggerClass="bg-transparent text-xs font-bold px-3 py-2 text-slate-200 border-r border-slate-800 focus:outline-none hover:bg-slate-900 cursor-pointer uppercase h-full min-w-[90px]"
        />
        <input 
            type="text" 
            v-model="store.activeRequest.url"
            placeholder="https://api.example.com/v1/users"
            class="flex-1 bg-transparent border-none focus:ring-0 text-sm text-slate-200 px-3 py-2 placeholder-slate-600 h-full"
            @keydown.enter="executeRequest"
        >
      </div>
      <button 
        v-if="!isLoading"
        @click="executeRequest"
        class="bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2 rounded-lg font-medium text-sm flex items-center gap-2 transition-all shadow-lg shadow-indigo-900/20"
      >
        <Play class="w-4 h-4 fill-current" />
        Send
      </button>
      <button
        v-else
        @click="cancelRequest"
        class="bg-rose-600 hover:bg-rose-500 text-white px-4 py-2 rounded-lg font-medium text-sm flex items-center gap-2 transition-all shadow-lg shadow-rose-900/20"
      >
        <Loader2 class="w-4 h-4 animate-spin" />
        <Square class="w-3.5 h-3.5 fill-current" />
        Cancel
      </button>
    </div>

    <div
      v-if="store.requestError"
      class="mx-4 mt-4 rounded-lg border border-rose-500/30 bg-rose-500/10 px-3 py-2 text-sm text-rose-200"
    >
      <div class="font-medium uppercase tracking-wide text-[11px] text-rose-300/80">{{ store.requestError.code }}</div>
      <div class="mt-1">{{ store.requestError.message }}</div>
    </div>

    <!-- Configuration Tabs -->
    <div class="flex border-b border-slate-800">
      <button 
        v-for="tab in ['params', 'headers', 'body', 'auth']" 
        :key="tab"
        @click="activeTab = tab"
        class="px-4 py-2 text-xs font-medium uppercase tracking-wide border-b-2 transition-colors"
        :class="activeTab === tab ? 'border-indigo-500 text-indigo-400 bg-indigo-500/5' : 'border-transparent text-slate-500 hover:text-slate-300 hover:bg-slate-800/50'"
      >
        {{ tab }}
      </button>
    </div>

    <!-- Content Area -->
    <div class="flex-1 overflow-hidden relative">
        <KeepAlive>
            <KeyValueEditor 
                v-if="activeTab === 'params'" 
                v-model="store.activeRequest.params" 
            />
        </KeepAlive>
        <KeepAlive>
            <KeyValueEditor 
                v-if="activeTab === 'headers'" 
                v-model="store.activeRequest.headers" 
            />
        </KeepAlive>
        <div v-if="activeTab === 'body'" class="h-full w-full overflow-hidden">
            <div class="flex items-center justify-between border-b border-slate-800 px-4 py-2 text-xs">
              <div class="text-slate-500">
                Mode: <span class="text-slate-300 uppercase">{{ inferredBodyLanguage }}</span>
              </div>
              <CustomSelect
                v-model="bodyMode"
                :options="bodyModeOptions"
                triggerClass="bg-slate-950 text-xs font-medium px-3 py-1.5 text-slate-200 border border-slate-800 rounded-md focus:outline-none hover:bg-slate-900 cursor-pointer min-w-[88px]"
              />
            </div>
            <div
              v-if="!methodSupportsBody"
              class="mx-4 mt-4 rounded-lg border border-amber-500/20 bg-amber-500/10 px-3 py-2 text-sm text-amber-100"
            >
              {{ store.activeRequest.method }} requests usually do not send a body. The editor stays available, but some servers may ignore it.
            </div>
            <CodeEditor
                v-model="store.activeRequest.body"
                :placeholder="requestBodyPlaceholder"
                :language="inferredBodyLanguage"
            />
        </div>
        <KeepAlive>
            <AuthPanel
                v-if="activeTab === 'auth'" 
            />
        </KeepAlive>
    </div>
  </div>
</template>
