/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  AppAddress,
  asError,
  Bytes,
  causedBy,
  Diagnostic,
  HttpService,
  InternalError,
  Logger,
  MatterError,
  NotImplementedError,
  Stream
} from "@matter/general";
import { StatusResponse, StatusResponseError } from "@matter/types";
import { Api } from "../remote/api/Api.js";
import { ApiPath } from "../remote/api/ApiPath.js";
import { RemoteInterface } from "../remote/RemoteInterface.js";
const logger = Logger.get("HttpInterface");
class HttpInterface extends RemoteInterface {
  static protocol = "http";
  #http;
  async start() {
    this.#http = await this.env.get(HttpService).create(this);
    this.#http.http = this.#handleRequest.bind(this);
  }
  async stop() {
    await this.#http?.close();
  }
  async #handleRequest(request) {
    let response;
    try {
      const address = new AppAddress(request.url);
      const path = this.root.subpathFor(new ApiPath(address));
      if (!path) {
        return;
      }
      response = await this.node.act("http", async (agent) => {
        const resource = await Api.resourceFor(agent, path);
        if (resource === void 0) {
          throw new StatusResponse.NotFoundError(`Path "${address.pathname}" not found`);
        }
        return this.#applyRequestToItem(request, resource);
      });
      logSuccess(request, response);
      return response;
    } catch (e) {
      const response2 = adaptError(e);
      logError(request, response2, e);
      return response2;
    }
  }
  async #applyRequestToItem(request, item) {
    switch (request.method) {
      case "GET": {
        const responseEnv = item.read();
        if (responseEnv === void 0) {
          throw new NotImplementedError();
        }
        return adaptResponse(request, responseEnv);
      }
      case "POST": {
        const requestEnv = await adaptRequest(request);
        if (item.isInvocable) {
          const responseEnv = await item.invoke(requestEnv);
          if (responseEnv === void 0) {
            return ok();
          }
          return adaptResponse(request, responseEnv);
        }
        if (item.isSubscribable) {
          return adaptResponse(request, item.subscribe(this.abort, requestEnv));
        }
        await item.add(requestEnv);
        return ok();
      }
      case "PUT": {
        const requestPayload = await adaptRequest(request);
        item.write(requestPayload);
        return ok();
      }
      case "PATCH": {
        const requestPayload = await adaptRequest(request);
        item.write(requestPayload);
        return ok();
      }
      case "DELETE":
        await item.delete();
        break;
    }
    throw new NotImplementedError();
  }
}
class UnnacceptableError extends MatterError {
}
class UnsupportedRequestContentTypeError extends MatterError {
}
const ErrorMappings = [
  [StatusResponse.UnsupportedAccessError, 401],
  [StatusResponse.NotFoundError, 404],
  [NotImplementedError, 405],
  [UnnacceptableError, 406],
  [UnsupportedRequestContentTypeError, 415]
];
const StatusCode = {
  200: "OK",
  400: "Bad Request",
  401: "Unauthorized",
  404: "Not Found",
  405: "Method Not Allowed",
  406: "Unnacceptable",
  415: "Unsupported Media Type",
  500: "Internal Server Error"
};
const JSON_CONTENT_TYPE = "application/json";
const JSONL_CONTENT_TYPE = "application/jsonl";
const TLV_CONTENT_TYPE = "application/matter-tlv";
async function adaptRequest(request) {
  let contentType = request.headers.get("Content-Type");
  if (contentType) {
    contentType = contentType.split(";")[0].trim();
  } else {
    contentType = "application/json";
  }
  switch (contentType) {
    case JSON_CONTENT_TYPE:
      return { json: await request.text() };
    case TLV_CONTENT_TYPE:
      return { tlv: await request.bytes() };
    default:
      throw new UnsupportedRequestContentTypeError();
  }
}
function ok() {
  return new Response(null, {
    status: 200,
    statusText: StatusCode[200]
  });
}
function adaptResponse(request, response) {
  const streamingResponse = Symbol.asyncIterator in response;
  const accept = request.headers.get("Accept-Encoding");
  const encodings = accept?.split(",").map((encoding) => encoding.split(";")[0].trim());
  let contentType;
  if (encodings) {
    for (const encoding of encodings) {
      if (encoding === JSONL_CONTENT_TYPE) {
        if (streamingResponse) {
          contentType = encoding;
        } else {
          contentType = JSON_CONTENT_TYPE;
        }
        break;
      }
      if (!streamingResponse && encoding === JSON_CONTENT_TYPE || encoding === TLV_CONTENT_TYPE) {
        contentType = encoding;
        break;
      }
    }
    if (contentType === void 0) {
      throw new UnnacceptableError(`Cannot produce requested MIME type "${accept}"`);
    }
  } else if (streamingResponse) {
    contentType = JSONL_CONTENT_TYPE;
  } else {
    contentType = JSON_CONTENT_TYPE;
  }
  let body;
  if (streamingResponse) {
    body = Stream.from(EnvelopeStreamIterator(contentType, response));
  } else if (contentType === TLV_CONTENT_TYPE) {
    body = Bytes.exclusive(response.tlv);
  } else {
    body = response.json;
  }
  return new Response(
    body,
    {
      status: 200,
      statusText: StatusCode[200],
      headers: {
        "Content-Type": contentType
      }
    }
  );
}
function adaptError(e) {
  const error = asError(e);
  let status;
  for (const [type, typeStatus] of ErrorMappings) {
    if (error instanceof type) {
      status = typeStatus;
    }
  }
  if (status === void 0) {
    if (causedBy(e, StatusResponseError)) {
      status = 400;
    } else {
      status = 500;
    }
  }
  let code, message;
  if (status >= 400 && status < 500) {
    code = error.id ?? "unknown";
    message = error.bareMessage ?? error.message;
  } else {
    code = "internal";
    message = "Internal error";
  }
  return new Response(
    JSON.stringify({
      kind: "error",
      code,
      message
    }),
    {
      status,
      statusText: StatusCode[status] ?? "Error",
      headers: {
        "Content-Type": JSON_CONTENT_TYPE,
        "Error-Code": code
      }
    }
  );
}
function logSuccess(request, response) {
  logger.info(diagnosticHeaderFor(request, response));
}
function logError(request, response, error) {
  if (response.status >= 500 && response.status < 600) {
    logger.warn(diagnosticHeaderFor(request, response), error);
  } else if (error instanceof MatterError) {
    logger.warn(diagnosticHeaderFor(request, response), Diagnostic.errorMessage(error));
  } else {
    logger.warn(diagnosticHeaderFor(request, response), asError(error).message);
  }
}
function diagnosticHeaderFor(request, response) {
  return Diagnostic.squash("[", Diagnostic.strong(request.url), " ", request.method, " ", response.status, "]");
}
async function* EnvelopeStreamIterator(contentType, iterator) {
  switch (contentType) {
    case TLV_CONTENT_TYPE:
      for await (const envelope of iterator) {
        yield envelope.tlv;
      }
      break;
    case JSONL_CONTENT_TYPE:
      for await (const envelope of iterator) {
        yield `${envelope.json}
`;
      }
      break;
    default:
      throw new InternalError(`Unsupported streaming content type ${contentType}`);
  }
}
export {
  HttpInterface,
  JSONL_CONTENT_TYPE,
  JSON_CONTENT_TYPE,
  TLV_CONTENT_TYPE,
  UnnacceptableError,
  UnsupportedRequestContentTypeError
};
//# sourceMappingURL=HttpInterface.js.map
