"""Feature collections editor."""

import gws.ext.db.provider.postgres

import gws
import gws.types as t
import gws.base.api
import gws.base.db
import gws.base.model
import gws.base.style
import gws.base.template
import gws.lib.feature
import gws.lib.mime
import gws.lib.style
import gws.base.web.error


##


class ItemProps(gws.lib.feature.Props):
    type: str
    collectionUid: str


class DocumentProps(gws.lib.feature.Props):
    collectionUid: str


class CollectionProps(gws.lib.feature.Props):
    type: str
    items: t.List[ItemProps]
    documents: t.List[DocumentProps]


##

class ItemPrototypeConfig(gws.Config):
    type: str
    name: str
    dataModel: gws.base.model.Config
    style: t.Optional[gws.base.style.Config]  #: style for features
    icon: str = ''


class ItemPrototypeProps(gws.Props):
    type: str
    name: str
    dataModel: gws.base.model.ModelProps
    style: gws.base.style.StyleProps
    icon: str


class ItemPrototype(gws.Node):
    def configure(self):
        

        self.db: gws.ext.db.provider.postgres.Object = t.cast(gws.ext.db.provider.postgres.Object, None)
        self.table: gws.SqlTable = t.cast(gws.SqlTable, None)

        self.link_col: str = ''
        self.type_col: str = 'type'

        self.data_model: gws.IDataModel = t.cast(gws.IDataModel, self.create_child('gws.base.model', self.var('dataModel')))

        self.type = self.var('type')
        self.name = self.var('name')
        self.icon = gws.lib.style.parse_icon(self.var('icon'))

        p = self.var('style')
        self.style: gws.IStyle = (
            gws.base.style.from_config(p) if p
            else gws.base.style.from_props(gws.StyleProps(type='css', values=_DEFAULT_STYLE_VALUES)))

    @property
    def props(self):
        return ItemPrototypeProps(
            type=self.type,
            name=self.name,
            dataModel=self.data_model.props,
            style=self.style.props,
            icon=self.icon
        )

    def validate(self, fprops: gws.lib.feature.Props) -> t.List[gws.AttributeValidationFailure]:
        f = gws.lib.feature.from_props(fprops)
        return self.data_model.validate(f.attributes)

    def save(self, collection_uid: str, fprops: gws.lib.feature.Props):
        f = gws.lib.feature.from_props(fprops)
        f.attributes.append(gws.Attribute(
            name=self.link_col,
            value=collection_uid,
        ))
        f.attributes.append(gws.Attribute(
            name=self.type_col,
            value=self.type,
        ))
        if str(f.uid).isdigit():
            fs = self.db.edit_operation('update', self.table, [f])
        else:
            f.uid = None
            fs = self.db.edit_operation('insert', self.table, [f])
        return fs[0]

    def delete(self, item_uid):
        with self.db.connect() as conn:
            with conn.transaction():
                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.table.name)}
                WHERE 
                    {conn.quote_ident(self.table.key_column)} = %s
                ''', item_uid)

    ##


class CollectionPrototypeConfig(gws.Config):
    type: str
    name: str
    db: t.Optional[str]  #: database provider uid
    collectionTable: gws.base.db.SqlTableConfig  #: sql table configuration
    itemTable: gws.base.db.SqlTableConfig  #: sql table configuration
    documentTable: gws.base.db.SqlTableConfig  #: sql table configuration
    dataModel: t.Optional[gws.base.model.Config]
    items: t.List[ItemPrototypeConfig]
    linkColumn: str = 'collection_id'
    style: t.Optional[gws.base.style.Config]  #: style for collection center point


class CollectionPrototypeProps(gws.Props):
    type: str
    name: str
    dataModel: gws.base.model.ModelProps
    itemPrototypes: t.List[ItemPrototypeProps]
    style: gws.base.style.StyleProps


class UploadFile(gws.Data):
    data: bytes
    mimeType: str
    title: str
    filename: str


class CollectionPrototype(gws.Node):
    def configure(self):
        

        self.db = t.cast(gws.ext.db.provider.postgres.Object, gws.base.db.require_provider(self, 'gws.ext.db.provider.postgres'))
        self.table = self.db.configure_table(self.var('collectionTable'))
        self.item_table = self.db.configure_table(self.var('itemTable'))
        self.document_table = self.db.configure_table(self.var('documentTable'))

        self.link_col = self.var('linkColumn')
        self.type_col = 'type'

        p = self.var('dataModel') or self.db.table_data_model_config(self.table)
        self.data_model: gws.IDataModel = t.cast(gws.IDataModel, self.create_child('gws.base.model', p))

        self.type = self.var('type')
        self.name = self.var('name')

        self.item_prototypes = []
        for p in self.var('items'):
            ip = t.cast(ItemPrototype, self.create_child(ItemPrototype, p))
            ip.db = self.db
            ip.table = self.item_table
            ip.link_col = self.link_col
            self.item_prototypes.append(ip)

        p = self.var('style')
        self.style: gws.IStyle = (
            gws.base.style.from_config(p) if p
            else gws.base.style.from_props(gws.StyleProps(type='css', values=_DEFAULT_STYLE_VALUES)))

    @property
    def props(self):
        return CollectionPrototypeProps(
            type=self.type,
            name=self.name,
            dataModel=self.data_model.props,
            itemPrototypes=[f.props for f in self.item_prototypes],
            style=self.style.props,
        )

    def validate(self, fprops: gws.lib.feature.Props) -> t.List[gws.AttributeValidationFailure]:
        f = gws.lib.feature.from_props(fprops)
        return self.data_model.validate(f.attributes)

    def save(self, fprops: gws.lib.feature.Props):
        f = gws.lib.feature.from_props(fprops)
        f.attributes.append(gws.Attribute(
            name=self.type_col,
            value=self.type,
        ))
        if str(f.uid).isdigit():
            fs = self.db.edit_operation('update', self.table, [f])
        else:
            f.uid = None
            fs = self.db.edit_operation('insert', self.table, [f])
        return fs[0]

    def delete(self, collection_uid):
        with self.db.connect() as conn:
            with conn.transaction():
                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.item_table.name)}
                WHERE 
                    {conn.quote_ident(self.link_col)} = %s
                ''', [collection_uid])

                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.document_table.name)}
                WHERE 
                    {conn.quote_ident(self.link_col)} = %s
                ''', [collection_uid])

                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.table.name)}
                WHERE 
                    {conn.quote_ident(self.table.key_column)} = %s
                ''', [collection_uid])

    def delete_item(self, collection_uid, item_uid):
        with self.db.connect() as conn:
            with conn.transaction():
                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.item_table.name)}
                WHERE 
                    {conn.quote_ident(self.link_col)} = %s
                    AND {conn.quote_ident(self.item_table.key_column)} = %s
                ''', [collection_uid, item_uid])

    def delete_document(self, collection_uid, document_uid):
        with self.db.connect() as conn:
            with conn.transaction():
                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.document_table.name)}
                WHERE 
                    {conn.quote_ident(self.link_col)} = %s
                    AND {conn.quote_ident(self.document_table.key_column)} = %s
                ''', [collection_uid, document_uid])

    def get_collection_ids(self):
        colls = self.db.select(gws.SqlSelectArgs(table=self.table, extra_where=['type=%s', self.type]))
        return [str(c.uid) for c in colls]

    def get_collections(self):
        res = []

        collections = self.db.select(gws.SqlSelectArgs(table=self.table, extra_where=['type=%s', self.type]))
        items = []
        documents = []
        uids = [c.uid for c in collections]
        if uids:
            cond = self.link_col + ' IN (%s)' % ','.join('%s' for _ in collections)

            items = self.db.select(gws.SqlSelectArgs(
                table=self.item_table,
                extra_where=[cond] + uids,
            ))

            documents = self.db.select(gws.SqlSelectArgs(
                table=self.document_table,
                extra_where=[cond] + uids,
                columns=['id', self.link_col, 'title', 'mimetype', 'filename', 'size'],
            ))

        for coll in collections:
            coll.apply_data_model(self.data_model)
            props = coll.props
            props.type = self.type

            props.items = []
            props.documents = []

            for item in items:
                if str(item.attr(self.link_col)) == str(coll.uid):
                    itype = item.attr(self.type_col)
                    ip = self.item_prototype(itype)
                    if ip:
                        item.apply_data_model(ip.data_model)
                        # @TODO
                        for a in item.attributes:
                            if a.type == 'bytes':
                                a.value = None
                        iprops = item.props
                        iprops.type = itype
                        props.items.append(iprops)

            for doc in documents:
                if str(doc.attr(self.link_col)) == str(coll.uid):
                    props.documents.append(doc.props)

            res.append(props)

        return res

    def item_prototype(self, type):
        for ip in self.item_prototypes:
            if ip.type == type:
                return ip

    def upload_documents(self, collection_uid, files: t.List[UploadFile]):
        with self.db.connect() as conn:
            with conn.transaction():
                for f in files:
                    rec = {
                        self.link_col: collection_uid,
                        'title': f.title or f.filename,
                        'mimetype': gws.lib.mime.for_path(f.filename),
                        'data': f.data,
                        'filename': f.filename,
                        'size': len(f.data),
                    }

                    conn.insert_one(
                        self.document_table.name,
                        self.document_table.key_column,
                        rec,
                    )

    def get_document(self, collection_uid, document_uid):
        documents = self.db.select(gws.SqlSelectArgs(
            table=self.document_table,
            uids=[document_uid]
        ))
        for doc in documents:
            return doc


##


class Config(gws.WithAccess):
    """Collection editor action"""

    collections: t.List[CollectionPrototypeConfig]


##


class GetPrototypesResponse(gws.Response):
    collectionPrototypes: t.List[CollectionPrototypeProps]


class GetCollectionsParams(gws.Params):
    type: str


class GetCollectionsResponse(gws.Response):
    collections: t.List[CollectionProps]


class SaveCollectionParams(gws.Params):
    type: str
    feature: gws.lib.feature.Props


class SaveCollectionResponse(gws.Response):
    collectionUid: str


class ValidationResponse(gws.Response):
    failures: t.List[gws.AttributeValidationFailure]


class SaveItemParams(gws.Params):
    type: str
    collectionUid: str
    feature: gws.lib.feature.Props


class SaveItemResponse(gws.Response):
    collectionUid: str
    itemUid: str


class DeleteCollectionParams(gws.Params):
    collectionUid: str


class DeleteItemParams(gws.Params):
    collectionUid: str
    itemUid: str


class DeleteDocumentParams(gws.Params):
    collectionUid: str
    documentUid: str


class GetDocumentParams(gws.Params):
    collectionUid: str
    documentUid: str


class UploadDocumentsParams(gws.Params):
    collectionUid: str
    files: t.List[UploadFile]


class UploadDocumentsResponse(gws.Response):
    pass


##

_DEFAULT_STYLE_VALUES = {
    'fill': 'rgba(0,0,0,1)',
    'stroke': 'rgba(0,0,0,1)',
    'stoke_width': 1,
}


class Object(gws.base.api.Action):

    @property
    def props(self):
        return gws.Props(
            enabled=True,
        )

    def configure(self):
        

        self.db = t.cast(gws.ext.db.provider.postgres.Object, gws.base.db.require_provider(self, 'gws.ext.db.provider.postgres'))

        self.collection_prototypes: t.List[CollectionPrototype] = []
        for p in self.var('collections'):
            self.collection_prototypes.append(t.cast(CollectionPrototype, self.create_child(CollectionPrototype, p)))

    def api_get_prototypes(self, req: gws.IWebRequest, p: gws.Params) -> GetPrototypesResponse:
        return GetPrototypesResponse(collectionPrototypes=[cp.props for cp in self.collection_prototypes])

    def api_get_collections(self, req: gws.IWebRequest, p: GetCollectionsParams) -> GetCollectionsResponse:
        for cp in self.collection_prototypes:
            if cp.type == p.type:
                return GetCollectionsResponse(collections=cp.get_collections())
        raise gws.base.web.error.NotFound()

    def api_validate_collection(self, req: gws.IWebRequest, p: SaveCollectionParams) -> ValidationResponse:
        for cp in self.collection_prototypes:
            if cp.type == p.type:
                failures = cp.validate(p.feature)
                return ValidationResponse(failures=failures)
        raise gws.base.web.error.NotFound()

    def api_save_collection(self, req: gws.IWebRequest, p: SaveCollectionParams) -> SaveCollectionResponse:
        for cp in self.collection_prototypes:
            if cp.type == p.type:
                coll = cp.save(p.feature)
                return SaveCollectionResponse(collectionUid=coll.uid)
        raise gws.base.web.error.NotFound()

    def api_validate_item(self, req: gws.IWebRequest, p: SaveItemParams) -> ValidationResponse:
        cp, ip = self.collection_and_item(p)
        failures = ip.validate(p.feature)
        return ValidationResponse(failures=failures)

    def api_save_item(self, req: gws.IWebRequest, p: SaveItemParams) -> SaveItemResponse:
        cp, ip = self.collection_and_item(p)
        item = ip.save(p.collectionUid, p.feature)
        return SaveItemResponse(
            collectionUid=p.collectionUid,
            itemUid=item.uid
        )

    def api_delete_collection(self, req: gws.IWebRequest, p: DeleteCollectionParams) -> gws.Response:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()
        cp.delete(p.collectionUid)
        return gws.Response()

    def api_delete_item(self, req: gws.IWebRequest, p: DeleteItemParams) -> gws.Response:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()

        cp.delete_item(p.collectionUid, p.itemUid)
        return gws.Response()

    def api_upload_documents(self, req: gws.IWebRequest, p: UploadDocumentsParams) -> gws.Response:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()
        cp.upload_documents(p.collectionUid, p.files)
        return gws.Response();

    def api_delete_document(self, req: gws.IWebRequest, p: DeleteDocumentParams) -> gws.Response:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()

        cp.delete_document(p.collectionUid, p.documentUid)
        return gws.Response()

    def http_get_document(self, req: gws.IWebRequest, p: GetDocumentParams) -> gws.ContentResponse:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()

        doc = cp.get_document(p.collectionUid, p.documentUid)
        if not doc:
            raise gws.base.web.error.NotFound()

        return gws.ContentResponse(
            mime=doc.attr('mimetype'),
            content=doc.attr('data'),
            attachment_name=doc.attr('filename')
        )

    def collection_and_item(self, p: SaveItemParams):
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()

        ip = cp.item_prototype(p.type)
        if not ip:
            raise gws.base.web.error.NotFound()

        return cp, ip

    def collection_proto_from_collection_uid(self, collection_uid) -> CollectionPrototype:
        for cp in self.collection_prototypes:
            if collection_uid in cp.get_collection_ids():
                return cp

"""Feature collections editor."""

import gws.ext.db.provider.postgres

import gws
import gws.types as t
import gws.base.api
import gws.base.db
import gws.base.model
import gws.base.style
import gws.base.template
import gws.lib.feature
import gws.lib.mime
import gws.lib.style
import gws.base.web.error


##


class ItemProps(gws.lib.feature.Props):
    type: str
    collectionUid: str


class DocumentProps(gws.lib.feature.Props):
    collectionUid: str


class CollectionProps(gws.lib.feature.Props):
    type: str
    items: t.List[ItemProps]
    documents: t.List[DocumentProps]


##

class ItemPrototypeConfig(gws.Config):
    type: str
    name: str
    dataModel: gws.base.model.Config
    style: t.Optional[gws.base.style.Config]  #: style for features
    icon: str = ''


class ItemPrototypeProps(gws.Props):
    type: str
    name: str
    dataModel: gws.base.model.ModelProps
    style: gws.base.style.StyleProps
    icon: str


class ItemPrototype(gws.Node):
    def configure(self):
        

        self.db: gws.ext.db.provider.postgres.Object = t.cast(gws.ext.db.provider.postgres.Object, None)
        self.table: gws.SqlTable = t.cast(gws.SqlTable, None)

        self.link_col: str = ''
        self.type_col: str = 'type'

        self.data_model: gws.IDataModel = t.cast(gws.IDataModel, self.create_child('gws.base.model', self.var('dataModel')))

        self.type = self.var('type')
        self.name = self.var('name')
        self.icon = gws.lib.style.parse_icon(self.var('icon'))

        p = self.var('style')
        self.style: gws.IStyle = (
            gws.base.style.from_config(p) if p
            else gws.base.style.from_props(gws.StyleProps(type='css', values=_DEFAULT_STYLE_VALUES)))

    @property
    def props(self):
        return ItemPrototypeProps(
            type=self.type,
            name=self.name,
            dataModel=self.data_model.props,
            style=self.style.props,
            icon=self.icon
        )

    def validate(self, fprops: gws.lib.feature.Props) -> t.List[gws.AttributeValidationFailure]:
        f = gws.lib.feature.from_props(fprops)
        return self.data_model.validate(f.attributes)

    def save(self, collection_uid: str, fprops: gws.lib.feature.Props):
        f = gws.lib.feature.from_props(fprops)
        f.attributes.append(gws.Attribute(
            name=self.link_col,
            value=collection_uid,
        ))
        f.attributes.append(gws.Attribute(
            name=self.type_col,
            value=self.type,
        ))
        if str(f.uid).isdigit():
            fs = self.db.edit_operation('update', self.table, [f])
        else:
            f.uid = None
            fs = self.db.edit_operation('insert', self.table, [f])
        return fs[0]

    def delete(self, item_uid):
        with self.db.connect() as conn:
            with conn.transaction():
                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.table.name)}
                WHERE 
                    {conn.quote_ident(self.table.key_column)} = %s
                ''', item_uid)

    ##


class CollectionPrototypeConfig(gws.Config):
    type: str
    name: str
    db: t.Optional[str]  #: database provider uid
    collectionTable: gws.base.db.SqlTableConfig  #: sql table configuration
    itemTable: gws.base.db.SqlTableConfig  #: sql table configuration
    documentTable: gws.base.db.SqlTableConfig  #: sql table configuration
    dataModel: t.Optional[gws.base.model.Config]
    items: t.List[ItemPrototypeConfig]
    linkColumn: str = 'collection_id'
    style: t.Optional[gws.base.style.Config]  #: style for collection center point


class CollectionPrototypeProps(gws.Props):
    type: str
    name: str
    dataModel: gws.base.model.ModelProps
    itemPrototypes: t.List[ItemPrototypeProps]
    style: gws.base.style.StyleProps


class UploadFile(gws.Data):
    data: bytes
    mimeType: str
    title: str
    filename: str


class CollectionPrototype(gws.Node):
    def configure(self):
        

        self.db = t.cast(gws.ext.db.provider.postgres.Object, gws.base.db.require_provider(self, 'gws.ext.db.provider.postgres'))
        self.table = self.db.configure_table(self.var('collectionTable'))
        self.item_table = self.db.configure_table(self.var('itemTable'))
        self.document_table = self.db.configure_table(self.var('documentTable'))

        self.link_col = self.var('linkColumn')
        self.type_col = 'type'

        p = self.var('dataModel') or self.db.table_data_model_config(self.table)
        self.data_model: gws.IDataModel = t.cast(gws.IDataModel, self.create_child('gws.base.model', p))

        self.type = self.var('type')
        self.name = self.var('name')

        self.item_prototypes = []
        for p in self.var('items'):
            ip = t.cast(ItemPrototype, self.create_child(ItemPrototype, p))
            ip.db = self.db
            ip.table = self.item_table
            ip.link_col = self.link_col
            self.item_prototypes.append(ip)

        p = self.var('style')
        self.style: gws.IStyle = (
            gws.base.style.from_config(p) if p
            else gws.base.style.from_props(gws.StyleProps(type='css', values=_DEFAULT_STYLE_VALUES)))

    @property
    def props(self):
        return CollectionPrototypeProps(
            type=self.type,
            name=self.name,
            dataModel=self.data_model.props,
            itemPrototypes=[f.props for f in self.item_prototypes],
            style=self.style.props,
        )

    def validate(self, fprops: gws.lib.feature.Props) -> t.List[gws.AttributeValidationFailure]:
        f = gws.lib.feature.from_props(fprops)
        return self.data_model.validate(f.attributes)

    def save(self, fprops: gws.lib.feature.Props):
        f = gws.lib.feature.from_props(fprops)
        f.attributes.append(gws.Attribute(
            name=self.type_col,
            value=self.type,
        ))
        if str(f.uid).isdigit():
            fs = self.db.edit_operation('update', self.table, [f])
        else:
            f.uid = None
            fs = self.db.edit_operation('insert', self.table, [f])
        return fs[0]

    def delete(self, collection_uid):
        with self.db.connect() as conn:
            with conn.transaction():
                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.item_table.name)}
                WHERE 
                    {conn.quote_ident(self.link_col)} = %s
                ''', [collection_uid])

                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.document_table.name)}
                WHERE 
                    {conn.quote_ident(self.link_col)} = %s
                ''', [collection_uid])

                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.table.name)}
                WHERE 
                    {conn.quote_ident(self.table.key_column)} = %s
                ''', [collection_uid])

    def delete_item(self, collection_uid, item_uid):
        with self.db.connect() as conn:
            with conn.transaction():
                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.item_table.name)}
                WHERE 
                    {conn.quote_ident(self.link_col)} = %s
                    AND {conn.quote_ident(self.item_table.key_column)} = %s
                ''', [collection_uid, item_uid])

    def delete_document(self, collection_uid, document_uid):
        with self.db.connect() as conn:
            with conn.transaction():
                conn.execute(f'''DELETE FROM 
                    {conn.quote_table(self.document_table.name)}
                WHERE 
                    {conn.quote_ident(self.link_col)} = %s
                    AND {conn.quote_ident(self.document_table.key_column)} = %s
                ''', [collection_uid, document_uid])

    def get_collection_ids(self):
        colls = self.db.select(gws.SqlSelectArgs(table=self.table, extra_where=['type=%s', self.type]))
        return [str(c.uid) for c in colls]

    def get_collections(self):
        res = []

        collections = self.db.select(gws.SqlSelectArgs(table=self.table, extra_where=['type=%s', self.type]))
        items = []
        documents = []
        uids = [c.uid for c in collections]
        if uids:
            cond = self.link_col + ' IN (%s)' % ','.join('%s' for _ in collections)

            items = self.db.select(gws.SqlSelectArgs(
                table=self.item_table,
                extra_where=[cond] + uids,
            ))

            documents = self.db.select(gws.SqlSelectArgs(
                table=self.document_table,
                extra_where=[cond] + uids,
                columns=['id', self.link_col, 'title', 'mimetype', 'filename', 'size'],
            ))

        for coll in collections:
            coll.apply_data_model(self.data_model)
            props = coll.props
            props.type = self.type

            props.items = []
            props.documents = []

            for item in items:
                if str(item.attr(self.link_col)) == str(coll.uid):
                    itype = item.attr(self.type_col)
                    ip = self.item_prototype(itype)
                    if ip:
                        item.apply_data_model(ip.data_model)
                        # @TODO
                        for a in item.attributes:
                            if a.type == 'bytes':
                                a.value = None
                        iprops = item.props
                        iprops.type = itype
                        props.items.append(iprops)

            for doc in documents:
                if str(doc.attr(self.link_col)) == str(coll.uid):
                    props.documents.append(doc.props)

            res.append(props)

        return res

    def item_prototype(self, type):
        for ip in self.item_prototypes:
            if ip.type == type:
                return ip

    def upload_documents(self, collection_uid, files: t.List[UploadFile]):
        with self.db.connect() as conn:
            with conn.transaction():
                for f in files:
                    rec = {
                        self.link_col: collection_uid,
                        'title': f.title or f.filename,
                        'mimetype': gws.lib.mime.for_path(f.filename),
                        'data': f.data,
                        'filename': f.filename,
                        'size': len(f.data),
                    }

                    conn.insert_one(
                        self.document_table.name,
                        self.document_table.key_column,
                        rec,
                    )

    def get_document(self, collection_uid, document_uid):
        documents = self.db.select(gws.SqlSelectArgs(
            table=self.document_table,
            uids=[document_uid]
        ))
        for doc in documents:
            return doc


##


class Config(gws.WithAccess):
    """Collection editor action"""

    collections: t.List[CollectionPrototypeConfig]


##


class GetPrototypesResponse(gws.Response):
    collectionPrototypes: t.List[CollectionPrototypeProps]


class GetCollectionsParams(gws.Params):
    type: str


class GetCollectionsResponse(gws.Response):
    collections: t.List[CollectionProps]


class SaveCollectionParams(gws.Params):
    type: str
    feature: gws.lib.feature.Props


class SaveCollectionResponse(gws.Response):
    collectionUid: str


class ValidationResponse(gws.Response):
    failures: t.List[gws.AttributeValidationFailure]


class SaveItemParams(gws.Params):
    type: str
    collectionUid: str
    feature: gws.lib.feature.Props


class SaveItemResponse(gws.Response):
    collectionUid: str
    itemUid: str


class DeleteCollectionParams(gws.Params):
    collectionUid: str


class DeleteItemParams(gws.Params):
    collectionUid: str
    itemUid: str


class DeleteDocumentParams(gws.Params):
    collectionUid: str
    documentUid: str


class GetDocumentParams(gws.Params):
    collectionUid: str
    documentUid: str


class UploadDocumentsParams(gws.Params):
    collectionUid: str
    files: t.List[UploadFile]


class UploadDocumentsResponse(gws.Response):
    pass


##

_DEFAULT_STYLE_VALUES = {
    'fill': 'rgba(0,0,0,1)',
    'stroke': 'rgba(0,0,0,1)',
    'stoke_width': 1,
}


class Object(gws.base.api.Action):

    @property
    def props(self):
        return gws.Props(
            enabled=True,
        )

    def configure(self):
        

        self.db = t.cast(gws.ext.db.provider.postgres.Object, gws.base.db.require_provider(self, 'gws.ext.db.provider.postgres'))

        self.collection_prototypes: t.List[CollectionPrototype] = []
        for p in self.var('collections'):
            self.collection_prototypes.append(t.cast(CollectionPrototype, self.create_child(CollectionPrototype, p)))

    def api_get_prototypes(self, req: gws.IWebRequest, p: gws.Params) -> GetPrototypesResponse:
        return GetPrototypesResponse(collectionPrototypes=[cp.props for cp in self.collection_prototypes])

    def api_get_collections(self, req: gws.IWebRequest, p: GetCollectionsParams) -> GetCollectionsResponse:
        for cp in self.collection_prototypes:
            if cp.type == p.type:
                return GetCollectionsResponse(collections=cp.get_collections())
        raise gws.base.web.error.NotFound()

    def api_validate_collection(self, req: gws.IWebRequest, p: SaveCollectionParams) -> ValidationResponse:
        for cp in self.collection_prototypes:
            if cp.type == p.type:
                failures = cp.validate(p.feature)
                return ValidationResponse(failures=failures)
        raise gws.base.web.error.NotFound()

    def api_save_collection(self, req: gws.IWebRequest, p: SaveCollectionParams) -> SaveCollectionResponse:
        for cp in self.collection_prototypes:
            if cp.type == p.type:
                coll = cp.save(p.feature)
                return SaveCollectionResponse(collectionUid=coll.uid)
        raise gws.base.web.error.NotFound()

    def api_validate_item(self, req: gws.IWebRequest, p: SaveItemParams) -> ValidationResponse:
        cp, ip = self.collection_and_item(p)
        failures = ip.validate(p.feature)
        return ValidationResponse(failures=failures)

    def api_save_item(self, req: gws.IWebRequest, p: SaveItemParams) -> SaveItemResponse:
        cp, ip = self.collection_and_item(p)
        item = ip.save(p.collectionUid, p.feature)
        return SaveItemResponse(
            collectionUid=p.collectionUid,
            itemUid=item.uid
        )

    def api_delete_collection(self, req: gws.IWebRequest, p: DeleteCollectionParams) -> gws.Response:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()
        cp.delete(p.collectionUid)
        return gws.Response()

    def api_delete_item(self, req: gws.IWebRequest, p: DeleteItemParams) -> gws.Response:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()

        cp.delete_item(p.collectionUid, p.itemUid)
        return gws.Response()

    def api_upload_documents(self, req: gws.IWebRequest, p: UploadDocumentsParams) -> gws.Response:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()
        cp.upload_documents(p.collectionUid, p.files)
        return gws.Response();

    def api_delete_document(self, req: gws.IWebRequest, p: DeleteDocumentParams) -> gws.Response:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()

        cp.delete_document(p.collectionUid, p.documentUid)
        return gws.Response()

    def http_get_document(self, req: gws.IWebRequest, p: GetDocumentParams) -> gws.ContentResponse:
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()

        doc = cp.get_document(p.collectionUid, p.documentUid)
        if not doc:
            raise gws.base.web.error.NotFound()

        return gws.ContentResponse(
            mime=doc.attr('mimetype'),
            content=doc.attr('data'),
            attachment_name=doc.attr('filename')
        )

    def collection_and_item(self, p: SaveItemParams):
        cp = self.collection_proto_from_collection_uid(p.collectionUid)
        if not cp:
            raise gws.base.web.error.NotFound()

        ip = cp.item_prototype(p.type)
        if not ip:
            raise gws.base.web.error.NotFound()

        return cp, ip

    def collection_proto_from_collection_uid(self, collection_uid) -> CollectionPrototype:
        for cp in self.collection_prototypes:
            if collection_uid in cp.get_collection_ids():
                return cp

Feature collections editor. ## ## #: style for features DELETE FROM                      {conn.quote_table(self.table.name)}                 WHERE                      {conn.quote_ident(self.table.key_column)} = %s ## #: database provider uid #: sql table configuration #: sql table configuration #: sql table configuration #: style for collection center point DELETE FROM                      {conn.quote_table(self.item_table.name)}                 WHERE                      {conn.quote_ident(self.link_col)} = %s DELETE FROM                      {conn.quote_table(self.document_table.name)}                 WHERE                      {conn.quote_ident(self.link_col)} = %s DELETE FROM                      {conn.quote_table(self.table.name)}                 WHERE                      {conn.quote_ident(self.table.key_column)} = %s DELETE FROM                      {conn.quote_table(self.item_table.name)}                 WHERE                      {conn.quote_ident(self.link_col)} = %s                     AND {conn.quote_ident(self.item_table.key_column)} = %s DELETE FROM                      {conn.quote_table(self.document_table.name)}                 WHERE                      {conn.quote_ident(self.link_col)} = %s                     AND {conn.quote_ident(self.document_table.key_column)} = %s # @TODO ## Collection editor action ## ##