import { blockHeader } from '@polkadot-api/substrate-bindings';
import { Binary } from 'polkadot-api';
import { Subscription, Observable, map, toArray, filter, firstValueFrom, combineLatest } from 'rxjs';
import { blockStorage } from '../executor/chainToStorage.js';
import { resolveStorageOperation } from './chainHead_v1.js';
import { errorResponse, getParams, respond, getUuid } from './rpc_utils.js';

const archiveStorageEvent = (subscription, result) => ({
  jsonrpc: "2.0",
  method: "archive_v1_storageEvent",
  params: {
    subscription,
    result
  }
});
const getSingleParam = (req) => Array.isArray(req.params) ? req.params[0] : req.params && typeof req.params === "object" ? Object.values(req.params)[0] : void 0;
const sendStorageError = (con, subscription, subscriptionId, error) => {
  if (subscription.closed) return;
  const message = error instanceof Error ? error.message : String(error);
  con.send(
    archiveStorageEvent(subscriptionId, {
      event: "storageError",
      error: message
    })
  );
};
const internalError = (req, error) => errorResponse(req, {
  code: -32603,
  message: error instanceof Error ? error.message : String(error)
});
const archive_v1_body = async (con, req, { chain, source }) => {
  const { hash } = getParams(req, ["hash"]);
  const block = chain.getBlock(hash);
  if (block) {
    con.send(respond(req, block.body.map(Binary.toHex)));
    return;
  }
  try {
    const body = await source.archive?.getBody(hash);
    con.send(respond(req, body?.map(Binary.toHex) ?? null));
  } catch (error) {
    con.send(internalError(req, error));
  }
};
const archive_v1_call = async (con, req, { chain, source, getOptions }) => {
  const {
    hash,
    function: fnName,
    callParameters
  } = getParams(req, ["hash", "function", "callParameters"]);
  const block = chain.getBlock(hash);
  if (block) {
    try {
      const output = await chain.executor.runRuntimeCall({
        storage: blockStorage(chain, hash),
        call: fnName,
        params: callParameters,
        mockSignatureHost: getOptions().mockSignatureHost ? 1 : 0
      });
      con.send(respond(req, { success: true, value: output.result }));
    } catch (error) {
      con.send(internalError(req, error));
    }
    return;
  }
  try {
    const value = await source.archive?.call(hash, fnName, callParameters);
    con.send(respond(req, value == null ? null : { success: true, value }));
  } catch (error) {
    con.send(internalError(req, error));
  }
};
const archive_v1_finalizedHeight = async (con, req, { chain }) => {
  const [finalizedHash, blocks] = await firstValueFrom(
    combineLatest([chain.finalized$, chain.blocks$])
  );
  con.send(respond(req, blocks[finalizedHash].header.number));
};
const archive_v1_genesisHash = async (con, req, { source }) => {
  try {
    con.send(respond(req, (await source.getChainSpecData()).genesisHash));
  } catch (error) {
    con.send(internalError(req, error));
  }
};
const archive_v1_hashByHeight = async (con, req, { chain, source }) => {
  const { height } = getParams(req, ["height"]);
  const [finalizedHash, blocks] = await firstValueFrom(
    combineLatest([chain.finalized$, chain.blocks$])
  );
  const finalized = blocks[finalizedHash];
  if (finalized.header.number < height) {
    const result = [];
    let children = [blocks[finalizedHash]];
    while (children.length) {
      const block2 = children.pop();
      if (block2.header.number === height) {
        result.push(block2.hash);
      } else {
        children = [
          ...children,
          ...block2.children.map((hash) => blocks[hash]).filter((v) => v != null)
        ];
      }
    }
    return con.send(respond(req, result));
  }
  let block = finalized;
  while (block && block.header.number !== height) {
    block = blocks[block.parent];
  }
  if (block) {
    return con.send(respond(req, [block.hash]));
  }
  try {
    const result = await source.archive?.getHashByHeight(height) ?? [];
    con.send(respond(req, result));
  } catch (error) {
    con.send(internalError(req, error));
  }
};
const archive_v1_header = async (con, req, { chain, source }) => {
  const { hash } = getParams(req, ["hash"]);
  const block = chain.getBlock(hash);
  if (block) {
    con.send(respond(req, Binary.toHex(blockHeader.enc(block.header))));
    return;
  }
  try {
    const header = await source.archive?.getHeader(hash);
    con.send(
      respond(req, header ? Binary.toHex(blockHeader.enc(header)) : null)
    );
  } catch (error) {
    con.send(internalError(req, error));
  }
};
const archive_v1_storage = (con, req, { chain, source }) => {
  const { hash, items, childTrie } = getParams(req, [
    "hash",
    "items",
    "childTrie"
  ]);
  if (childTrie)
    return con.send(
      errorResponse(req, {
        code: -1,
        message: "Child trie not implemented"
      })
    );
  const subscriptionId = getUuid();
  const subscription = new Subscription();
  con.context.archive_v1_storage_subs[subscriptionId] = subscription;
  const cleanup = () => {
    delete con.context.archive_v1_storage_subs[subscriptionId];
    subscription.unsubscribe();
  };
  con.send(respond(req, subscriptionId));
  const items$ = chain.getBlock(hash) ? resolveStorageOperation(chain, hash, items) : (() => {
    if (!source.archive) {
      throw new Error("Archive source not available");
    }
    return new Observable((obs) => {
      source.archive.storageSubscription(
        hash,
        items,
        childTrie,
        (item) => obs.next(item),
        (e) => obs.error(e),
        () => obs.complete()
      );
    }).pipe(
      map((item) => item),
      toArray(),
      filter((v) => v.length > 0)
    );
  })();
  subscription.add(
    items$.subscribe({
      next: (items2) => items2.forEach(
        (item) => con.send(
          archiveStorageEvent(subscriptionId, {
            event: "storage",
            ...item
          })
        )
      ),
      error: (error) => {
        sendStorageError(con, subscription, subscriptionId, error);
        cleanup();
      },
      complete: () => {
        con.send(archiveStorageEvent(subscriptionId, { event: "storageDone" }));
        cleanup();
      }
    })
  );
};
const archive_v1_stopStorage = (con, req) => {
  const subscriptionId = getSingleParam(req);
  con.send(respond(req, null));
  if (!subscriptionId) return;
  con.context.archive_v1_storage_subs[subscriptionId]?.unsubscribe();
};
const archive_v1_storageDiff = (con, req) => {
  con.send(
    errorResponse(req, {
      code: -1,
      message: "Not implemented"
    })
  );
};
const archive_v1_stopStorageDiff = (con, req) => {
  con.send(
    errorResponse(req, {
      code: -1,
      message: "Not implemented"
    })
  );
};

export { archive_v1_body, archive_v1_call, archive_v1_finalizedHeight, archive_v1_genesisHash, archive_v1_hashByHeight, archive_v1_header, archive_v1_stopStorage, archive_v1_stopStorageDiff, archive_v1_storage, archive_v1_storageDiff };
//# sourceMappingURL=archive_v1.js.map
