<?php 
namespace PortalPeople\netsuite\controllers;
use Yii;
use backend\models\Cartsbe;
use yii\web\ForbiddenHttpException;
class NetsuiteController extends \yii\web\Controller
{
  private const ELIGIBLE_CART_STATUSES = ['Paid', 'Processed'];

  private function canManageNetSuite(): bool
  {
    $identity = Yii::$app->user->identity ?? null;

    if (!$identity || !method_exists($identity, 'inGroup')) {
      return false;
    }

    return $identity->inGroup('Global Admins') || $identity->inGroup('Sales Admin');
  }

  public function actionBulkprocess()
  {
    if (!$this->canManageNetSuite()) {
      throw new ForbiddenHttpException('You do not have permission to process NetSuite orders.');
    }

    $request = Yii::$app->request;
    $filters = [
      'date' => (string) ($request->post('date') ?? $request->get('date') ?? date('Y-m-d 00:00:00', strtotime('-7 days'))),
      'accountCode' => (string) ($request->post('accountCode') ?? $request->get('accountCode') ?? 'QVC001'),
      'limit' => (int) ($request->post('limit') ?? $request->get('limit') ?? 100),
      'forceNetSuitePush' => (bool) ($request->post('forceNetSuitePush') ?? $request->get('forceNetSuitePush') ?? false),
    ];

    $result = null;

    if ($request->isPost) {
      $result = $this->runBulkProcess($filters['date'], $filters['accountCode'], $filters['limit'], $filters['forceNetSuitePush']);
    }

    return $this->render('@PortalPeople/netsuite/views/netsuite/bulk-process', [
      'filters' => $filters,
      'result' => $result,
    ]);
  }

  private function resolveCartModelClass(?string $accountCode = null): string
  {
    $netsuiteConfig = Yii::$app->params['netsuite'] ?? [];
    $defaultCartClass = $netsuiteConfig['cartClass'] ?? Cartsbe::class;

    $accountClassMap = [
      'GAR001' => \PortalPeople\magento\models\Carts::class,
      'QVC001' => \PortalPeople\qvc\models\Cart::class,
      'SCR001' => 'PortalPeople\\virtualstock\\models\\Cart',
    ];

    $candidateClass = $accountClassMap[$accountCode] ?? $defaultCartClass;

    return class_exists($candidateClass) ? $candidateClass : $defaultCartClass;
  }

  private function findCartModel($id)
  {
    $baseCart = Cartsbe::findOne($id);
    if (!$baseCart) {
      return null;
    }

    $cartClass = $this->resolveCartModelClass($baseCart->account_code ?? null);

    return $cartClass::findOne($id, 'en') ?? $baseCart;
  }

  private function normaliseChanges(array $changes): array
  {
    return array_map(static function (array $change): array {
      $fields = [];
      foreach (($change['fields'] ?? []) as $field => $values) {
        $fields[] = [
          'field' => $field,
          'old' => $values['old'] ?? null,
          'new' => $values['new'] ?? null,
        ];
      }

      return [
        'lineId' => $change['lineId'] ?? null,
        'lineType' => $change['lineType'] ?? null,
        'productName' => $change['productName'] ?? null,
        'changeType' => $change['changeType'] ?? 'updated',
        'fields' => $fields,
      ];
    }, $changes);
  }

  private function buildChangeSummary(array $changes): array
  {
    $summary = [];

    foreach ($changes as $change) {
      $parts = [];
      foreach (($change['fields'] ?? []) as $field) {
        $parts[] = sprintf(
          '%s: %s -> %s',
          $field['field'],
          $this->stringifyChangeValue($field['old'] ?? null),
          $this->stringifyChangeValue($field['new'] ?? null)
        );
      }

      $summary[] = sprintf(
        '%s [%s] %s%s',
        $change['productName'] ?: 'Line',
        $change['lineId'] ?: 'unknown',
        $change['changeType'],
        empty($parts) ? '' : ' | ' . implode(', ', $parts)
      );
    }

    return $summary;
  }

  private function stringifyChangeValue($value): string
  {
    if ($value === null) {
      return 'null';
    }

    if (is_bool($value)) {
      return $value ? 'true' : 'false';
    }

    if (is_scalar($value)) {
      return (string) $value;
    }

    if (is_object($value) && method_exists($value, '__toString')) {
      return (string) $value;
    }

    $encoded = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    return $encoded === false ? gettype($value) : $encoded;
  }

  private function runBulkProcess(string $date, string $accountCode, int $limit, bool $forceNetSuitePush = false): array
  {
    $timestamp = strtotime($date);
    if ($timestamp === false) {
      return [
        'ok' => false,
        'message' => "Invalid date: {$date}",
        'filters' => compact('date', 'accountCode', 'limit'),
        'rows' => [],
        'summary' => [],
      ];
    }

    $cartClass = $this->resolveCartModelClass($accountCode ?: null);
    if (!method_exists($cartClass, 'recalculateCartlineCostsAndSyncNetSuite')) {
      return [
        'ok' => false,
        'message' => "Cart class {$cartClass} does not support recalculation and NetSuite sync.",
        'filters' => compact('date', 'accountCode', 'limit'),
        'rows' => [],
        'summary' => [],
      ];
    }

    $cartIds = $this->buildBulkCartQuery($cartClass, $timestamp, $accountCode, $limit)
      ->select('id')
      ->column();

    $rows = [];
    $processed = 0;
    $changed = 0;
    $netsuiteUpdated = 0;
    $errors = 0;

    @set_time_limit(0);

    foreach ($cartIds as $cartId) {
      try {
        $cart = $cartClass::find()
          ->andWhere(['id' => $cartId])
          ->andWhere(['status' => self::ELIGIBLE_CART_STATUSES])
          ->one();
        if (!$cart) {
          $rows[] = [
            'cartId' => $cartId,
            'status' => 'skipped',
            'message' => 'Cart no longer exists or is no longer eligible.',
            'changed' => false,
            'netsuiteUpdated' => false,
            'changedLineIds' => [],
            'summary' => [],
          ];
          continue;
        }

        $result = $cart->recalculateCartlineCostsAndSyncNetSuite($forceNetSuitePush);
        $processed++;

        $rowChanged = (bool) ($result['changed'] ?? false);
        $rowNetsuiteUpdated = (bool) ($result['netsuiteUpdated'] ?? false);
        $changes = $this->normaliseChanges($result['changes'] ?? []);
        $summary = $this->buildChangeSummary($changes);

        if ($rowChanged) {
          $changed++;
        }

        if ($rowNetsuiteUpdated) {
          $netsuiteUpdated++;
        }

        $rows[] = [
          'cartId' => $cartId,
          'status' => 'processed',
          'message' => empty($changes)
            ? ($rowNetsuiteUpdated && $forceNetSuitePush
              ? 'No cart line cost changes detected, but NetSuite order was force-updated successfully.'
              : 'No cart line cost changes detected.')
            : ($rowNetsuiteUpdated
              ? 'Cart costs recalculated and NetSuite order updated successfully.'
              : 'Cart costs recalculated, but NetSuite order update was skipped.'),
          'changed' => $rowChanged,
          'netsuiteUpdated' => $rowNetsuiteUpdated,
          'forced' => !empty($result['forced']),
          'changedLineIds' => $result['changedLineIds'] ?? [],
          'summary' => $summary,
          'netsuiteResult' => $result['netsuiteResult'] ?? [],
        ];
      } catch (\Throwable $e) {
        $errors++;
        Yii::error([
          'message' => 'Web NetSuite bulk processing failed',
          'cartId' => $cartId,
          'error' => $e->getMessage(),
        ], 'netsuite.web.bulk-process');

        $rows[] = [
          'cartId' => $cartId,
          'status' => 'error',
          'message' => $e->getMessage(),
          'changed' => false,
          'netsuiteUpdated' => false,
          'changedLineIds' => [],
          'summary' => [],
        ];
      }
    }

    return [
      'ok' => true,
      'message' => sprintf('Processed %d cart(s).', count($cartIds)),
      'filters' => [
        'date' => date('Y-m-d H:i:s', $timestamp),
        'accountCode' => $accountCode,
        'limit' => $limit,
        'forceNetSuitePush' => $forceNetSuitePush,
      ],
      'summary' => [
        'matched' => count($cartIds),
        'processed' => $processed,
        'changed' => $changed,
        'netsuiteUpdated' => $netsuiteUpdated,
        'errors' => $errors,
        'forced' => $forceNetSuitePush,
      ],
      'rows' => $rows,
      'cartClass' => $cartClass,
    ];
  }

  private function buildBulkCartQuery(string $cartClass, int $timestamp, string $accountCode, int $limit)
  {
    $query = $cartClass::find()
      ->andWhere(['>=', 'dateordered', $timestamp])
      ->andWhere(['status' => self::ELIGIBLE_CART_STATUSES])
      ->orderBy(['dateordered' => SORT_ASC]);

    if ($accountCode !== '') {
      $query->andWhere(['account_code' => $accountCode]);
    }

    if ($limit > 0) {
      $query->limit($limit);
    }

    return $query;
  }

     /**
     * Links Order to Netsuite, Pops a modal.
     *
     * @param [type] $id
     * @return void
     */
    public function actionLinktons($id)
    {
      $model = $this->findCartModel($id);
      if (!$model) {
        return ['status' => 100, 'Message' => 'Cart not found.'];
      }
        if (!is_object($model->data) && method_exists($model, 'loadJson')) {
          try {
            $model->loadJson();
          } catch (\Throwable $_) {
            // ignore
          }
        }
        $netsuite = is_object($model->data) ? ($model->data->netsuite ?? null) : null;
        Yii::$app->response->format = 'json';
        if (Yii::$app->request->isGet == true) {
            return $this->renderAjax('@PortalPeople/netsuite/views/cart/linktons', ['model' => $model, 'netsuite' => $netsuite]);
        } elseif (Yii::$app->request->isPost == true && $netsuite->load(Yii::$app->request->post()) && $netsuite->validate() && $model->save()) {
            return ['status' => 200, 'Message' => 'Linked'];
        } elseif (Yii::$app->request->isPost) {
            return ['status' => 100, 'Message' => 'Error'];
        }
    }

    public function actionSendordertonetsuite() {
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        $id = Yii::$app->request->post('id');
        $sendPayload = Yii::$app->request->post('sendPayload', 'true');
        $model = $this->findCartModel($id);
        if ($model) {
            try {
                $result = \PortalPeople\netsuite\components\NetSuite::SendOrderToNetsuite($id, $sendPayload);
                if ($sendPayload === 'false' || $sendPayload === false) {
                    return ['status' => 200, 'Message' => 'Payload generated successfully.', 'payload' => $result->content ?? null];
                }
                // Handle response - check statusCode (HttpClient) or status (custom object)
                $statusCode = $result->statusCode ?? $result->status ?? 0;
                
                if ((int)$statusCode >= 200 && (int)$statusCode < 300) {
                    $message = 'Order sent to NetSuite successfully.';
                    if (!empty($result->already_exists)) {
                        $message = 'Order already exists in NetSuite.';
                    }
                    return ['status' => 200, 'Message' => $message];
                }else{
                    $errorMsg = isset($result->content) ? $result->content : 'Unknown error';
                    if (is_object($errorMsg)) {
                        $errorMsg = json_encode($errorMsg);
                    }
                    return ['status' => 100, 'Message' => 'Failed to send order to NetSuite: ' . $errorMsg];
                }
            } catch (\Exception $e) {
                Yii::error("Error sending order to NetSuite: " . $e->getMessage());
                return ['status' => 100, 'Message' => 'Error sending to NetSuite: ' . $e->getMessage()];
            }
        }else{
            return ['status' => 100, 'Message' => 'Cart not found.'];
        }
    }

      public function actionUpdatecartcosts()
      {
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        $id = Yii::$app->request->post('id') ?? Yii::$app->request->get('id');
        $model = $this->findCartModel($id);

        if (!$model) {
          return ['status' => 100, 'Message' => 'Cart not found.'];
        }

        if (!method_exists($model, 'recalculateCartlineCosts')) {
          return ['status' => 100, 'Message' => 'Cart does not support cost recalculation.'];
        }

        try {
          $result = $model->recalculateCartlineCosts();
          $changes = $this->normaliseChanges($result['changes'] ?? []);

          return [
            'status' => 200,
            'Message' => !empty($changes)
              ? 'Cart lines recalculated successfully.'
              : 'No cart line cost changes detected.',
            'changed' => !empty($changes),
            'changes' => $changes,
            'summary' => $this->buildChangeSummary($changes),
          ];
        } catch (\Throwable $e) {
          Yii::error('Error updating cart costs: ' . $e->getMessage());
          return ['status' => 100, 'Message' => 'Error updating cart costs: ' . $e->getMessage()];
        }
      }

      public function actionUpdatenetsuiteorder()
      {
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        $id = Yii::$app->request->post('id') ?? Yii::$app->request->get('id');
        $model = $this->findCartModel($id);

        if (!$model) {
          return ['status' => 100, 'Message' => 'Cart not found.'];
        }

        try {
          $result = \PortalPeople\netsuite\components\NetSuite::UpdateExistingOrderLines($id);

          if (!empty($result['updated'])) {
            return [
              'status' => 200,
              'Message' => 'NetSuite order lines updated successfully.',
              'netsuiteResult' => $result,
            ];
          }

          return [
            'status' => 100,
            'Message' => 'NetSuite order update skipped: ' . ($result['reason'] ?? 'unknown'),
            'netsuiteResult' => $result,
          ];
        } catch (\Throwable $e) {
          Yii::error($e, 'Error updating NetSuite order lines: ');
          Yii::error('Error updating NetSuite order lines: ' . $e->getMessage());
          return ['status' => 100, 'Message' => 'Error updating NetSuite order lines: ' . $e->getMessage()];
        }
      }

      public function actionUpdatecartandnetsuite()
      {
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        $id = Yii::$app->request->post('id') ?? Yii::$app->request->get('id');
        $model = $this->findCartModel($id);

        if (!$model) {
          return ['status' => 100, 'Message' => 'Cart not found.'];
        }

        if (!method_exists($model, 'recalculateCartlineCostsAndSyncNetSuite')) {
          return ['status' => 100, 'Message' => 'Cart does not support combined sync.'];
        }

        try {
          $forceNetSuitePush = filter_var(Yii::$app->request->post('forceNetSuitePush') ?? Yii::$app->request->get('forceNetSuitePush') ?? false, FILTER_VALIDATE_BOOLEAN);
          $result = $model->recalculateCartlineCostsAndSyncNetSuite($forceNetSuitePush);
          $changes = $this->normaliseChanges($result['changes'] ?? []);
          $netsuiteUpdated = !empty($result['netsuiteUpdated']);
          $message = empty($changes)
            ? ($netsuiteUpdated && $forceNetSuitePush
              ? 'No cart line cost changes detected, but NetSuite order was force-updated successfully.'
              : 'No cart line cost changes detected.')
            : ($netsuiteUpdated
              ? 'Cart costs recalculated and NetSuite order updated successfully.'
              : 'Cart costs recalculated, but NetSuite order update was skipped.');

          return [
            'status' => 200,
            'Message' => $message,
            'changed' => !empty($changes),
            'changes' => $changes,
            'summary' => $this->buildChangeSummary($changes),
            'netsuiteUpdated' => $netsuiteUpdated,
            'forced' => !empty($result['forced']),
            'netsuiteResult' => $result['netsuiteResult'] ?? [],
          ];
        } catch (\Throwable $e) {
          Yii::error('Error updating cart and NetSuite: ' . $e->getMessage());
          return ['status' => 100, 'Message' => 'Error updating cart and NetSuite: ' . $e->getMessage()];
        }
      }

    public function actionGetorder() {
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        $id = Yii::$app->request->get('id') ?? Yii::$app->request->post('id');
        if (!$id) {
            return ['status' => 100, 'Message' => 'No cart ID provided.'];
        }

        $model = $this->findCartModel($id);
        if (!$model) {
            return ['status' => 100, 'Message' => 'Cart not found.'];
        }

        $externalId = $model->id . '/F';
        $order = \PortalPeople\netsuite\components\NetSuite::GetSalesOrderByExternalId($externalId);

        if ($order === null) {
            return ['status' => 404, 'Message' => "No NetSuite sales order found for externalId: {$externalId}"];
        }

        return ['status' => 200, 'order' => $order];
    }

    public function actionGetorderfromnetsuite($id) {
        
      return \PortalPeople\netsuite\components\NetSuite::GetSendOrderfromNetsuite($id);
    }

    public function actionUpdateDelivered($id) {
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        $model = $this->findCartModel($id);
        if (!$model) {
            return ['status' => 100, 'Message' => 'Cart not found.'];
        }

        try {
            $result = \PortalPeople\netsuite\components\NetSuite::Updateorder_deliver($id);
            return [
                'status' => 200,
                'Message' => 'Delivered status updated successfully.',
                'netsuiteResult' => $result,
            ];
        } catch (\Throwable $e) {
            Yii::error('Error updating delivered status: ' . $e->getMessage());
            return ['status' => 100, 'Message' => 'Error updating delivered status: ' . $e->getMessage()];
        }
    }
    public function actionGetordermeta() {
        $this->layout = false;

        $raw = Yii::$app->netsuiteClient->getMetadataCatalog('salesorder');
        $properties = $raw['properties'] ?? [];
        $fields = [];

        foreach ($properties as $key => $prop) {
            $type = $prop['type'] ?? 'object';

            if ($type === 'object' && isset($prop['properties']['items']['type']) && $prop['properties']['items']['type'] === 'array') {
                $subFields = [];
                foreach ($prop['properties']['items']['items']['properties'] ?? [] as $subKey => $subProp) {
                    if (in_array($subKey, ['links', 'refName'])) continue;
                    $subFields[] = [
                        'field'    => $subKey,
                        'title'    => $subProp['title'] ?? null,
                        'type'     => $subProp['type'] ?? 'object',
                        'format'   => $subProp['format'] ?? null,
                        'nullable' => $subProp['nullable'] ?? false,
                        'custom'   => isset($subProp['x-ns-custom-field']),
                    ];
                }
                $fields[$key] = [
                    'field'     => $key,
                    'title'     => $prop['title'] ?? null,
                    'type'      => 'sublist',
                    'subFields' => $subFields,
                    'custom'    => isset($prop['x-ns-custom-field']),
                ];
                continue;
            }

            $fields[$key] = [
                'field'       => $key,
                'title'       => $prop['title'] ?? null,
                'type'        => $type,
                'format'      => $prop['format'] ?? null,
                'nullable'    => $prop['nullable'] ?? false,
                'custom'      => isset($prop['x-ns-custom-field']),
                'enum'        => $prop['properties']['id']['enum'] ?? ($prop['enum'] ?? null),
                'description' => isset($prop['description']) ? substr(strip_tags($prop['description']), 0, 120) : null,
            ];
        }

        uasort($fields, function($a, $b) {
            if ($a['custom'] !== $b['custom']) return $a['custom'] ? 1 : -1;
            return strcmp($a['field'], $b['field']);
        });

        $totalFields    = count($fields);
        $standardFields = count(array_filter($fields, fn($f) => !$f['custom']));
        $customFields   = count(array_filter($fields, fn($f) => $f['custom']));

        $typeColour = function(string $type): string {
            return match($type) {
                'string'  => '#0d6efd',
                'integer' => '#198754',
                'number'  => '#198754',
                'boolean' => '#dc3545',
                'object'  => '#6f42c1',
                'sublist' => '#fd7e14',
                default   => '#6c757d',
            };
        };

        ob_start(); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NetSuite Sales Order Metadata</title>
<style>
  *, *::before, *::after { box-sizing: border-box; }
  body { font-family: system-ui, sans-serif; font-size: 13px; background: #f8f9fa; color: #212529; margin: 0; padding: 20px; }
  h1 { font-size: 1.4rem; margin-bottom: 4px; }
  .stats { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
  .stat { background: #fff; border: 1px solid #dee2e6; border-radius: 6px; padding: 8px 16px; text-align: center; }
  .stat strong { display: block; font-size: 1.5rem; }
  .stat span { color: #6c757d; font-size: 11px; }
  .controls { margin-bottom: 12px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
  input[type=search] { padding: 5px 10px; border: 1px solid #ced4da; border-radius: 4px; font-size: 13px; width: 260px; }
  label { font-size: 12px; display: flex; align-items: center; gap: 4px; cursor: pointer; }
  table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,.1); }
  thead th { background: #212529; color: #fff; padding: 8px 10px; text-align: left; white-space: nowrap; position: sticky; top: 0; }
  tbody tr:nth-child(even) { background: #f8f9fa; }
  tbody tr:hover { background: #e9ecef; }
  td { padding: 6px 10px; vertical-align: top; border-bottom: 1px solid #dee2e6; }
  .badge { display: inline-block; padding: 1px 7px; border-radius: 20px; font-size: 11px; font-weight: 600; color: #fff; white-space: nowrap; }
  .badge-custom { background: #6f42c1; }
  .badge-null  { background: #adb5bd; }
  code { font-size: 12px; background: #e9ecef; padding: 1px 5px; border-radius: 3px; }
  .field-name { font-family: monospace; font-size: 12px; }
  details summary { cursor: pointer; color: #fd7e14; font-size: 11px; }
  details table { margin-top: 4px; }
  details thead th { background: #fd7e14; font-size: 11px; }
  .hidden { display: none; }
</style>
</head>
<body>
<h1>NetSuite &mdash; Sales Order Field Metadata</h1>
<div class="stats">
  <div class="stat"><strong><?= $totalFields ?></strong><span>Total Fields</span></div>
  <div class="stat"><strong><?= $standardFields ?></strong><span>Standard</span></div>
  <div class="stat"><strong><?= $customFields ?></strong><span>Custom (custbody_*)</span></div>
</div>
<div class="controls">
  <input type="search" id="search" placeholder="Search field name or title&hellip;" oninput="filterTable()">
  <label><input type="checkbox" id="hideCustom" onchange="filterTable()"> Hide custom fields</label>
  <label><input type="checkbox" id="hideStandard" onchange="filterTable()"> Hide standard fields</label>
</div>
<table id="metaTable">
  <thead>
    <tr>
      <th>#</th>
      <th>Field Name</th>
      <th>Title</th>
      <th>Type</th>
      <th>Format</th>
      <th>Nullable</th>
      <th>Custom</th>
      <th>Enum / Description</th>
    </tr>
  </thead>
  <tbody>
<?php $i = 0; foreach ($fields as $f): $i++; ?>
    <tr data-custom="<?= $f['custom'] ? '1' : '0' ?>">
      <td><?= $i ?></td>
      <td><span class="field-name"><?= htmlspecialchars($f['field']) ?></span></td>
      <td><?= htmlspecialchars((string)($f['title'] ?? '')) ?></td>
      <td><span class="badge" style="background:<?= $typeColour($f['type']) ?>"><?= htmlspecialchars($f['type']) ?></span></td>
      <td><?= !empty($f['format'] ?? null) ? '<code>'.htmlspecialchars($f['format']).'</code>' : '' ?></td>
      <td><?= ($f['nullable'] ?? false) ? '<span class="badge badge-null">null</span>' : '' ?></td>
      <td><?= $f['custom'] ? '<span class="badge badge-custom">custom</span>' : '' ?></td>
      <td>
        <?php if ($f['type'] === 'sublist' && !empty($f['subFields'])): ?>
          <details>
            <summary><?= count($f['subFields']) ?> sub-fields</summary>
            <table>
              <thead><tr><th>Field</th><th>Title</th><th>Type</th><th>Format</th><th>Custom</th></tr></thead>
              <tbody>
              <?php foreach ($f['subFields'] as $sf): ?>
                <tr>
                  <td class="field-name"><?= htmlspecialchars($sf['field']) ?></td>
                  <td><?= htmlspecialchars((string)($sf['title'] ?? '')) ?></td>
                  <td><span class="badge" style="background:<?= $typeColour($sf['type']) ?>"><?= htmlspecialchars($sf['type']) ?></span></td>
                  <td><?= $sf['format'] ? '<code>'.htmlspecialchars($sf['format']).'</code>' : '' ?></td>
                  <td><?= $sf['custom'] ? '<span class="badge badge-custom">custom</span>' : '' ?></td>
                </tr>
              <?php endforeach; ?>
              </tbody>
            </table>
          </details>
        <?php elseif (!empty($f['enum'])): ?>
          <details>
            <summary><?= count($f['enum']) ?> enum values</summary>
            <div style="font-size:11px;margin-top:4px"><?= implode(', ', array_map('htmlspecialchars', $f['enum'])) ?></div>
          </details>
        <?php else: ?>
          <span style="color:#6c757d;font-size:11px"><?= htmlspecialchars((string)($f['description'] ?? '')) ?></span>
        <?php endif; ?>
      </td>
    </tr>
<?php endforeach; ?>
  </tbody>
</table>
<script>
function filterTable() {
  const q = document.getElementById('search').value.toLowerCase();
  const hideCustom = document.getElementById('hideCustom').checked;
  const hideStandard = document.getElementById('hideStandard').checked;
  document.querySelectorAll('#metaTable tbody tr').foreach (row => {
    const isCustom = row.dataset.custom === '1';
    const text = row.innerText.toLowerCase();
    const matchSearch = !q || text.includes(q);
    const matchFilter = !(hideCustom && isCustom) && !(hideStandard && !isCustom);
    row.classList.toggle('hidden', !matchSearch || !matchFilter);
  });
}
</script>
</body>
</html>
<?php
        return ob_get_clean();
    }
}
