# Encode

The `Encode` macro generates JSON encoding methods with **cycle detection** and object identity
tracking. This enables encoding of complex object graphs including circular references.

## Generated Methods

| Type       | Generated Code                                                                  | Description                                 |
| ---------- | ------------------------------------------------------------------------------- | ------------------------------------------- |
| Class      | `classNameEncode(value, keepMetadata?)` + `static encode(value, keepMetadata?)` | Standalone function + static wrapper method |
| Enum       | `enumNameEncode(value)`, `enumNameEncodeWithContext`                            | Standalone functions                        |
| Interface  | `interfaceNameEncode(value)`, etc.                                              | Standalone functions                        |
| Type Alias | `typeNameEncode(value)`, etc.                                                   | Standalone functions                        |

## Cycle Detection Protocol

The generated code handles circular references using `__id` and `__ref` markers:

```json
{
    "__type": "User",
    "__id": 1,
    "name": "Alice",
    "friend": { "__ref": 2 } // Reference to object with __id: 2
}
```

When an object is encoded:

1. Check if it's already been encoded (has an `__id`)
2. If so, return `{ "__ref": existingId }` instead
3. Otherwise, register the object and encode its fields

## Type-Specific Encoding

| Type                                      | Encoding Strategy                                                                |
| ----------------------------------------- | -------------------------------------------------------------------------------- |
| Primitives                                | Direct value                                                                     |
| `Date`                                    | `toISOString()`                                                                  |
| Arrays                                    | For primitive-like element types, pass through; for `Date`/`Date                 |
| `Map<K,V>`                                | For primitive-like values, `Object.fromEntries(map.entries())`; for `Date`/`Date |
| `Set<T>`                                  | Convert to array; element handling matches `Array<T>`                            |
| `Record<K,V>`                             | For primitive-like values, pass through; for `Date`/`Date                        |
| Wrappers (`Partial<T>`, `Pick<T,K>`, ...) | Encode based on the inner type `T`                                               |
| Nullable                                  | Include `null` explicitly; non-null values follow the inner type's strategy      |
| Objects                                   | Call the type's `nameEncodeWithContext` function                                 |

Note: which strategy applies is resolved **statically** from the field's declared TypeScript type at
expansion time: there is no runtime feature detection.

## Field-Level Options

The `@endec` decorator supports:

- `skip` / `skipEncoding` - Exclude field from encoding
- `rename = "jsonKey"` - Use different JSON property name
- `flatten` - Merge nested object's fields into parent

## Example

```typescript before
/** @derive(Encode) */
class User {
    id: number;

    /** @endec({ rename: "userName" }) */
    name: string;

    /** @endec({ skipEncoding: true }) */
    password: string;

    /** @endec({ flatten: true }) */
    metadata: UserMetadata;
}
```

```typescript after
import { EncodeContext } from '@macroforge/core/endec';

class User {
    id: number;

    name: string;

    password: string;

    metadata: UserMetadata;
    /** Encodes a value to a JSON string.
@param value - The value to encode
@returns JSON string representation with cycle detection metadata  */

    static encode(value: User, keepMetadata?: boolean): string {
        return userEncode(value, keepMetadata);
    }
    /** @internal Encodes with an existing context for nested/cyclic object graphs.
@param value - The value to encode
@param ctx - The encoding context  */

    static encodeWithContext(value: User, ctx: __mf_EncodeContext): Record<string, unknown> {
        return userEncodeWithContext(value, ctx);
    }
}

/** Encodes a value to a JSON string.
@param value - The value to encode
@returns JSON string representation with cycle detection metadata */ export function userEncode(
    value: User,
    keepMetadata?: boolean
): string {
    const ctx = __mf_EncodeContext.create();
    const __raw = userEncodeWithContext(value, ctx);
    if (keepMetadata) return JSON.stringify(__raw);
    return JSON.stringify(
        __raw,
        (key, val) => key === '__type' || key === '__id' ? undefined : val
    );
} /** @internal Encodes with an existing context for nested/cyclic object graphs.
@param value - The value to encode
@param ctx - The encoding context */

export function userEncodeWithContext(
    value: User,
    ctx: EncodeContext
): Record<string, unknown> {
    const existingId = ctx.getId(value);
    if (existingId !== undefined) {
        return { __ref: existingId };
    }
    const __id = ctx.register(value);
    const result: Record<string, unknown> = { __type: 'User', __id };
    result['id'] = value.id;
    result['userName'] = value.name;
    {
        const __flattened = userMetadataEncodeWithContext(value.metadata, ctx);
        const { __type: _, __id: __, ...rest } = __flattened as any; // tag field name is configurable via `tag` option
        Object.assign(result, rest);
    }
    return result;
}
```

## Required Import

The generated code automatically imports `EncodeContext` from `@macroforge/core/endec`.
